Unit testing generally adds a sizeable chunk of time to development. Here is an approach that, with a bit of leg work in the beginning can make writing unit tests seriously easy.
Part of the key to the simple unit testing
The existing solutions for unit test are (correctly) very generic and can cater for varying scenarios. But, building large systems generally means we follow patterns. If we know and understand these patterns then it is quite easy to build a testing framework to suit the system.
For this example I'll assume that we'll follow the fairly standard pattern for a WCF service. A service takes some kind of IRepository as a constructor argument. The IRepository, in this very simplified example knows how to get an entity by id, get all users, and log a user in.
The aim is to write unit tests for the service that look something like:
public void GetUser_CanFetchExistingUser() { ExpectResultUsing(TestClass.GetUser, -1, result =>; result == null); }
Back to knowing our system. We can know that for the design of our system a repository can serve (and save, update) a set of entities. For our tests we'll assume a entity set size of 20 entities.
Start with an abstract base unit test class:
public abstract class BaseUnitTest
where TTestClass is the class we're testing.
During construction, this base unit test creates an instance of the service it is testing with a little help in the form of an abstract method:
public abstract TTestClass GetInstance();
So, for our example the UserServiceTest class starts to look as follows:
[TestFixture] public class UserServiceTest : BaseUnitTest { private const int NumberOfUsers = 20; public override IUserService GetInstance() { // Build the dependencies Mocks.MockUserRepository userRepository = new Mocks.MockUserRepository(NumberOfUsers); // Return a user service with its mocked dependencies return new UserService(userRepository); } }
As you can see, the UserServiceTest knows how to create an instance, with a mock repository, of the UserService. Note that the number of users passed to the MockUserRepository is the entity set size of 20 mentioned earlier.
I'll explain in a bit, but first let's list the important parts of the MockUserRepository so far.
public class MockUserRepository : MockRepositoryBase, IUserRepository { public MockUserRepository(int dataSetSize) : base(dataSetSize) { } protected override User BuildEntity(int id) { // The pattern here for string properties in general is "PropertyName_Id" return new User { Id = id, Username = string.Format("Username_{0}", id), Password = string.Format("Password_{0}", id) }; }
Again you'll see the pattern that the MockUserRepository knows how to create an instance of the entity that it holds. An important piece of the puzzle is conventions. For this framework we'll stick to the convention that, for a mock entity, the value of string property is "PropertyName_EntityId" i.e. user with id == 1 has a Username == "Username_1". This is what allows us to make assumptions in our tests about valid or invalid arguments being passed to the service. For example, we have set up the repository with an entity set size of 20, so we can therefore assume when testing that asking for a user with id==42 should return null. All of our mock repositories behave as very much simplified versions of their real equivalents.
The MockRepositoryBase will be expected to create the entity set and therefore looks like this:
public abstract class MockRepositoryBase : IRepository where TEntity : IEntity { protected IList _dataSet; protected MockRepositoryBase(int dataSetSize) { BuildDataSet(dataSetSize); } private void BuildDataSet(int dataSetSize) { _dataSet = new List(); for (int i = 0; i < dataSetSize; i++) { _dataSet.Add(BuildEntity(i)); } } }
Now, we have a very simple pattern for mock repositories in general. The MockRepositoryBase will therefore have methods to perform basic actions on the entity, for example:
public TEntity Get(int id) { return (from entity in _dataSet where entity.Id == id select entity).SingleOrDefault(); }
In our case, the UserRepository must be able to log a user in. To achieve in ours tests, this we create a very simplified version of the login method in the MockUserRepository(disclaimer: Yes I know logins don't work like this at all, but this is an example!). It quite simple searches the created entity set of 20 entities for a matching user.
public bool Login(string username, string password) { // Idiot checks if (string.IsNullOrEmpty(username)) return false; if (string.IsNullOrEmpty(password)) return false;</code> // Find the user User user = (from u in _dataSet where u.Username == username where u.Password == password select u).SingleOrDefault(); // Success if we found a matching user return user != null; }
Recap
Now we have a working a UserServiceTest class which knows how to create a UserService with a MockUserRepository. The MockUserRepository has 20 users with known naming conventions. Still, we want to be able to write those AWESOME single line unit tests! Thanks to the magic of generics, our work is made very easy. We need to add some methods to the base unit test class. I'll show one that specifically works for the Get(int id) method:
protected TResult ExpectResultUsing( Func<TArg1, TResult> method, // Method arguments TArg1 arg1, Func<TResult, bool> expectationPredicate) { // Execute TResult result = method.Invoke(arg1); // Ensure the expectation is fulfilled Assert.IsTrue(expectationPredicate.Invoke(result)); return result; }
I'll show again how it is called
ExpectResultUsing(TestClass.GetUser, -1, result =>; result == null);
WAWAWIWA that was good!
I'll explain. The type arguments for the method can generally be resolved by the "method" argument that is passed in. The "method" argument passed in in this case
TestClass.GetUser
This is followed by the arguments in order (thanks to the generic type arguments that the method requires), in this case the id. The final argument is a predicate that defines our expectation. In this case TestClass.GetUser returns an object of type User. So our predicate is
result => result == null
Now, everything is in place for (importantly) the known system and the known conventions. The test class is created with dependencies, ready for testing with these one liners. Of course we'll need Setup and TearDown methods to reset the repository between tests.
Lesson learned here...
We can take some of the ungh out of writing unit tests if we're willing to take some time to build a framework that works with the system. More methods like the generic
ExpectResultUsing
would need to be added for methods accepting more arguments.
The testing framework used here is not intended as a generic solution to all testing. Testing frameworks must be built to suit the system being built. Hopefully this will give you a push in the right direction. Also, this is a new framework and I fully expect changes and tweaks to be made along the way along with the growth of the system being tested.
Download the VS2010 solution of the example code