Background
When writing the unit test for a specific type, that type often depends on other specific types, often domain objects themselves. I have often seen unit tests construct these domain objects and setting required properties as necessary to set up the fixture required for that specific test.
In some cases that can be fine, but if the required parameters for a constructor changes, you can end up with many unit tests having to be modified to reflect this change. You can also be in a position where some properties need to be set within a range of valid values in order for the object itself to be in a valid state required for the test. As the system is build upon, the criteria that makes up a valid domain object can change, and that can ripple through the entire test suite.
In a recent project I worked on, we had a very strict programming model for the domain objects. All properties on a domain object that was either required or read-only had to be passed as a constructor parameter. This ensured two things. When a new domain object has been constructed, we could be sure that would be in a consistent state, i.e. no non-nullable values would be null or unassigned. We could also be sure that read-only properties could not be modified.
That programming model has both its advantages and its disadvantages. It makes it a little less flexible for unit tests. And the afforementioned problem can become a big problem, as the paramteters for the constructor changes regualarly.
Another problem that could arise would be that certain combinations of properties would be invalid. E.g. we had a class that had a numeric value property, but also numeric MinValue and MaxValue properties that specified the valid range of values for the Value property. Setting the Value property to something outside the valid range would throw an exception.
Therefore if one test requires a specific value for the Value property, that test would also have to set the MinValue and MaxValue properties even though the actual value of those properties have no bearing on the outcome of the test.
This would result in tests being polluted as the extra property initialization code hides from the reader what is important for this particular function being tested, especially seen from the test-as-documentation point of view.
Therefore from very early on in the project we relied on creating helper classes in order to construct domain objects in the required state, and ensuring that the returned domain object was in a valid state. So asking this helper class for an object with a specific Value, it would automatically make sure the MinValue and MaxValue properties was set to contain the required Value property value.
As the project progressed, we developed a builder pattern relying on lambdas for constructing the domain objects. Although the pattern required a little more code to begin with, this pattern resulted in clean and easily maintanable unit tests. And the pattern resulted in a high level code of reuse.
I will not be reusing code from that actual project, but I will make some examples using easyly understood domain objects.
The Pattern
A simple builder class that could build a User domain object could look like this:
public class UserBuilder { private string _firstName; private string _lastName; public void SetFirstName(string firstName) { _firstName = firstName; } public void SetLastName(string lastName) { _lastName = lastName; } public User GetUser() { return new User { FirstName = _firstName; LastName = _lastName; }; } public static User Build(Action<UserBuilder> buildAction = null) { var builder = new UserBuilder(); if (buildAction != null) buildAction(builder); return builder.GetUser(); } }
A simple unit test could look like this
[TestFixture] public class UserFormTest { [Test] public void ShouldInitializeFirstNameTextBox() { var user = UserBuilder.Build(x => x.SetFirstName("First")); var form = new UserForm(user); Assert.That(form.FirstName.Text, Is.EqualTo("First")); } [Test] public void FormShouldBeValidWhenValidUserEntered() { var user = UserBuilder.Build(); Assume.That(user.Valid); var form = new UserForm(user); Assert.That(form.Valid, Is.True); } }
Let's look at what the first test communicates to the reader. It communicates that a UserForm requires a user. It also communicates that the first name of the user is of importance to the outcome of the test.
The last tast is based on the assumption that the UserBuilder always returns user that is a valid object.
But lets say we add a new property, Email, to the User class, and a value for this property is required for the User to be valid. This addition will cause the last test to fail.
But lets add to the user builder:
public class UserBuilder { private string _email; ... public void SetEmail(string email) { _email = email; } public User GetUser() { return new User { FirstName = _firstName; LastName = _lastName; Email = _email ?? "johndoe@example.com"; }; } }
Now, the UserBuilder will always return a User with an initialized email address. If a test requires the email to have a specific value, it can set the value, otherwise a dummy value will be used.
Reusability of build actions.
Lets look at a different type of system. Imagine that we are building an ASP.NET MVC application, and we are writing the UserController. In this case the controller returns a view model that the view can render. Some prefer to always wrap the data the view requires in a view model; others find it to be a waste of code. But in this case the controller returns a view model simply in order to have a very simple test to demonstrate.
[TestFixture] public class UserControllerTest { Mock<IUserRepository> _userRepositoryMock; UserController _controller; [SetUp] public void Setup() { _repositoryMock = new Mock<IUserRepository>(); _controller = new UserController(_repositoryMock.Object); } [Test] public void ShouldReturnViewModelWithCorrectFirstName() { var user = UserBuilder.Build(x => {x.SetId(42); x.SetFirstName("First"); }); _repositoryMock.Setup(x => x.Get(42)).Returns(user); var viewModel = _controller.Detail(42).ViewData.Model as UserViewModel; Assert.That(viewModel.FirstName, Is.EqualTo("first")); } [Test] public void ShouldReturnViewModelWithCorrectLastName() { var user = UserBuilder.Build(x => {x.SetId(42); x.SetLstName("Last"); }); _repositoryMock.Setup(x => x.Get(42)).Returns(user); var viewModel = _controller.Detail(42).ViewData.Model as UserViewModel; Assert.That(viewModel.LastName, Is.EqualTo("Last")); } }
One could say that all having a separate test for each separate property is a waste of code. You could just set up all required properties in one go, and have multiple asserts in the end of the test. Personally, I prefer to have a separate test for each assertion. But that requires a little less code duplication.
[TestFixture] public class UserControllerTest { Mock<IUserRepository> _userRepositoryMock; UserController _controller; [SetUp] public void Setup() { _repositoryMock = new Mock<IUserRepository>(); _controller = new UserController(_repositoryMock.Object); } public User SetupUserInMockRepository(Action<UserBuilder> buildAction) { var user = UserBuilder.Build(x => { x.SetId(42); buildAction(x); }; _repositoryMock.Setup(x => x.Get(42)).Returns(user); return user; } [Test] public void ShouldReturnViewModelWithCorrectFirstName() { SetupUserInMockRepository(x => x.SetFirstName("First")); var viewModel = _controller.Find(user.Id).ViewData.Model as UserViewModel; Assert.That(viewModel.FirstName, Is.EqualTo("First")); } [Test] public void ShouldReturnViewModelWithCorrectFirstName() { SetupUserInMockRepository(x => x.SetLastName("Last")); var viewModel = _controller.Find(user.Id).ViewData.Model as UserViewModel; Assert.That(viewModel.FirstName, Is.EqualTo("Last")); } }
What is interesting here is that the helper function here takes a build action, and adds to it. It clearly displays that it becomes very easy to create new helper functions don't take a User as a parameter, but instead a much more general specification about how the user should look.
Lets take another example, the repository iself. In this case the repository is also the data access layer. So unit testing this repository actually means storing and loading data from the database, verifying that the SQL queries are correct.
[TestFixture] public class UserRepositoryTest { public User CreateUserInDb(Action<UserBuilder> buildAction) { var user = UserBuilder.Build(buildAction); var repository = new UserRepository(); repository.AddUser(user); repository.Commit(); } // It is assumed that the database is cleared between each test. [Test] public void PartialSearchByFirstNameReturnsUser() { var user = CreateUserInDb(x => x.SetFirstName("First")); var repository = new UserRepository(); var users = repository.FindByFirstName("F"); Assert.That(users.Single().Id, Is.EqualTo(user.Id)); } [Test] public void PartialSearchByFirstNameDoesntReturnUser() { var user = CreateuserInDb(x => x.SetFirstName("First")); var repository = new UserRepository(); var users = repository.FindByFirstName("X"); Assert.That(users.Count(), Is.EqualTo(0)); } }
Nested builders
If your domain objects reference each other, you can let different builders use each other. Imagine a Document class that takes the User who created the document as constructor parameter, and the user cannot be null.
public class DocumentBuilder { List<Action<UserBuilder>> _userBuildActions = new List<Action<UserBuilder>>(); public void BuildUser(Action<UserBuilder> buildAction) { _userBuildActions.Add(buildAction); } ... public Document GetDocument() { var userBuilder = new UserBuilder(); _userBuildActions.ForEach(x => x(userBuilder)); var user = userBuilder.GetUser(); return new Document(User); } }
Using the this builder we can create a document and specify how the creator should look. E.g. in this test for a document form:
[TestFixture] public class DocumentFormTest { [Test] public void ShouldDisplayOwnerName() { var document = DocumentBuilder.Build(x => x.BuildUser(y => y.SetFullName("John doe"))); var documentForm = new DocumentForm(document); Assert.That(documentForm.UserNameLabel.Text, Is.EqualTo("John Doe")); } }
We can create some extension functions to make this test easier to read.
public static class DocumentBuilderExtensions { public static void SetCreatorFullName(this DocumentBuilder builder, string name) { builder.BuildUser(x => x.SetFullName(name)); } } [TestFixture] public class DocumentFormTest { [Test] public void ShouldDisplayCreatorName() { var document = DocumentBuilder.Build(x => x.SetCreatorFullName("John Doe")); var documentForm = new DocumentForm(document); Assert.That(documentForm.UserNameLabel.Text, Is.EqualTo("John Doe")); } }
Not only have the helper functions reduced the amount of code for the individual test to a minimum, it very clearly communicates its intent. The name of the person who created the document should be displayed on the form. It also abstracts from the test exactly how the owner name is stored.
But the most important quality of this pattern is that the unit tests relying on these builders to construct required domain objects have shown to require very little maintenance as the system is expanded and code is being refactored. That is a huge gain.