Today I was working on a module that actually had quite a good bit of business logic wrapped up in the business tier. This business class depended on other classes that made calls to other classes, the database, so on and so forth. We are not using a dependency injection framework currently (we are very aware of it and are looking into performance and other concerns before we implement one) but we use the factory pattern. It occurred to me as I was writing my unit tests that I shouldn't have to know about a database or provider layer or even set up a single row in a single table to make sure it all worked. What I needed was some good old-fashioned mock objects to do what I wanted, and some dependency injection to stuff them into my other classes.
Many people think of dependency injection in terms of frameworks such as Unity or StructureMap, rather than as a concept that can be implemented in some cases with very little overhead.
Let's take my example from today. The names have been changed to protect the innocent.
We'll start with some simple classes. I have contact in my system:
namespace ContactProject
{
public class ContactEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Building { get; set; }
public string Floor { get; set; }
}
}
Some of my contacts can actually sign into the system, so they have user accounts. The simple result looks like this:
Now we have some business classes. We are good developers and we interface those to expose the appropriate services. I have one that loads the user by the username. The username in turn calls the contact business object to load the contact information. When it's all said and done, we have something else. Now I know there are many, many things wrong with this example, so please just help me get to where I'm going with the mocking and the injection. Let's pretend I have a user business class that talks to a user data handler to get a user object, and also to get a contact first name and last name if we want that. It can then talk to the contact business to get the full contact with the first name and last name. That's my example and I'm sticking to it, because my point is you will end up with objects talking to factories and instantiated other classes, etc. Let's assume this:
namespace ContactProject
{
public class UserBusiness : IUserBusiness
{
public UserEntity Load(string username)
{
IUserDataHandler dataHandler = DataFactory.GetUserDataHandler();
UserEntity retVal = dataHandler.Load(username);
if (retVal != null && !string.IsNullOrEmpty(retVal.Username))
{
string firstName;
string lastName;
if (GetContactKey(username, out firstName, out lastName))
{
IContactBusiness contactBusiness = BusinessFactory.GetContactBusiness();
retVal.Contact = contactBusiness.Load(firstName, lastName);
}
}
return retVal;
}
public bool GetContactKey(string username, out string firstName, out string lastName)
{
// do something
firstName = "John";
lastName = "Doe";
return true;
}
}
}
So now I have another business class that takes a document for a user and saves it to disk. Let's say we are always saving it to d:\users\ but then the path should be organized by building, level, last name, and first name. Because I want to separate my concerns, I'll have one class worry about the logic to construct the path and let another class worry about persisting it to disk. I end up with this ...
namespace ContactProject
{
public class PathMaker
{
public string GetPathForUser(string username)
{
//did the caller honor our contract?
if (string.IsNullOrEmpty(username))
{
throw new ArgumentNullException("username");
}
IUserBusiness userBusiness = BusinessFactory.GetUserBusiness();
UserEntity user = userBusiness.Load(username);
if (user == null || string.IsNullOrEmpty(user.Username))
{
throw new Exception("User did not load.");
}
if (user.Contact == null)
{
throw new Exception("Contact information did not load.");
}
return string.Format(@"d:\test\{0}\{1}\{2}\{3}\",
user.Contact.Building,
user.Contact.Floor,
user.Contact.LastName,
user.Contact.FirstName);
}
}
}
Now I start to write my unit tests. This is when I start to get into trouble. The first issue I have is that, in order to control my results, I have to have something actually in my database. I might do something like use LINQ or SSIS to throw in a "known" user and a known contact, and then I could write a test to make sure my path is as expected. Doesn't seem quite right, does it?
So let's go back to my business class. Assume it's used all over the place and I haven't received approval to load an external framework like Unity. What do I do? First, we can refactor the class a bit so it behaves exactly as everyone expects it to, BUT allows for us to extend it a bit for testing.
namespace ContactProject
{
public class PathMaker
{
private IUserBusiness _userBusiness;
public PathMaker()
{
_userBusiness = BusinessFactory.GetUserBusiness();
}
public PathMaker(IUserBusiness userBusiness)
{
_userBusiness = userBusiness;
}
public string GetPathForUser(string username)
{
//did the caller honor our contract?
if (string.IsNullOrEmpty(username))
{
throw new ArgumentNullException("username");
}
UserEntity user = _userBusiness.Load(username);
if (user == null || string.IsNullOrEmpty(user.Username))
{
throw new Exception("User did not load.");
}
if (user.Contact == null)
{
throw new Exception("Contact information did not load.");
}
return string.Format(@"d:\test\{0}\{1}\{2}\{3}\",
user.Contact.Building,
user.Contact.Floor,
user.Contact.LastName,
user.Contact.FirstName);
}
}
}
See what I've done? The factory is still called and still gives us our interface to work with, but I've added a new constructor that lets me inject my own implementation. Now we can stub out a test user business class ...
namespace ContactTestProject
{
public class UserBusinessTest : IUserBusiness
{
public UserEntity Load(string username)
{
return new UserEntity
{
Username = username,
Password = "testpassword",
Contact = new ContactEntity
{
FirstName = "John",
LastName = "Doe",
Building = "Administration",
Floor = "B1"
}
};
}
public bool GetContactKey(string username, out string firstName, out string lastName)
{
throw new NotImplementedException();
}
}
}
And then test our path ...
[TestMethod]
public void TestValidPath()
{
// we expect the path to be d:\test\Administration\B1\Doe\John\
string target = @"d:\test\Administration\B1\Doe\John\";
PathMaker pathMaker = new PathMaker(new UserBusinessTest());
Assert.AreEqual(target,pathMaker.GetPathForUser("JohnDoe"));
}
That got me a nice green checkbox, but there are a few things that feel really wrong about this.
First, I have a nice comment about the expected path ... but how do I know that's the expected path? It seems like my "UserBusinessTest" is a blackbox now. I have to make some assumptions about it. What if someone else decides they need a test for level B2 and changes my test object? It will break my unit test ... but that's not the point! The test is that a valid path is made given any sort of user input, and shouldn't have to depend on a static user. I could add a switch statement and return a specific user for "JohnDoe" and comment my code to say, "Don't touch this user! Make your own!" but that's hardly enforceable.
It's time to mock!
Now before we get too excited and start looking at mocking frameworks, downloading them and reading the help text ... why not start by making our own? C# is the perfect language for building these without having to drag around a huge framework. We can get there eventually, but right now we just need to test a path. We already tackled dependency injection without having to get rid of our factory pattern or lug in an "Inversion of Control Framework" ... what can we do with Mock?
Let's roll up our sleeves and get started. What I want is a business object I can inject, but that gives me flexibility to predict and change my results. This sounds like a perfect case for using delegates. I can implement my well-known interface, then extend my object to expose delegates that let my test framework inject the desired results! Let's see what this looks like:
namespace ContactProject
{
public class UserBusinessMock : IUserBusiness
{
public delegate UserEntity TestLoad(string username);
public delegate bool TestGetContactKey(string username, out string firstName, out string lastName);
public TestLoad OverrideLoad { get; set; }
public TestGetContactKey OverrideGetContactKey { get; set; }
public UserEntity Load(string username)
{
if (OverrideLoad != null)
{
return OverrideLoad(username);
}
throw new NotImplementedException();
}
public bool GetContactKey(string username, out string firstName, out string lastName)
{
if (OverrideGetContactKey != null)
{
return OverrideGetContactKey(username, out firstName, out lastName);
}
throw new NotImplementedException();
}
}
}
Obviously this class won't do much for me right now - in fact, if I try to use it, I'll get plenty of errors. Following the tenants of test-driven design (TDD) we'll go ahead and plug it in, run it, and sure enough, our test fails. That's fine, now we must refactor.
What I want to do is let my TEST control the input, so that my TEST can control the expected result. In my class, I marked delegates that match the signature of the methods on the interface. Then I exposed those delegates. First, I must update my class to use them. I'll simply delegate the "load" method to return the user I want.
[TestMethod]
public void TestValidPath()
{
// we expect the path to be d:\test\Administration\B1\Doe\John\
string target = @"d:\test\Administration\B1\Doe\John\";
UserBusinessMock
userBusinessMock =
new UserBusinessMock
{
OverrideLoad =
username =>
new UserEntity
{
Username = username,
Password = "testpassword",
Contact =
new ContactEntity
{
FirstName = "John",
LastName = "Doe",
Building =
"Administration",
Floor = "B1"
}
}
};
PathMaker pathMaker = new PathMaker(userBusinessMock);
Assert.AreEqual(target, pathMaker.GetPathForUser("JohnDoe"));
}
I run my test ... and it's green! Keep in mind, too, this method will allow chaining, i.e. going as deep as you need by injecting up the chain for your tests. My business logic shouldn't care about the database, so I don't have to worry about dependencies on data handlers and factories, I just mock what I need and test what is important: the algorithm. The fetch from the database is handed off to another layer, and the unit test for that belongs there.
There it is ... and you thought delegates for just for events or callbacks! I've improved my unit tests by creating my own "mock" type class, delegates, and dependency injection ... but here's the best part. I didn't have to download anything from the web or figure out how to set up an XML configuration file just to wire in my dependencies ... nor did I have to go and refactor my production code because that all still runs "as is" blissfully ignorant of my ability to inject mocks for testing purposes.