I, Test with Mocks and Builders
Building and testing with Mocks and Builders
Any code for this blog can be found here: https://github.com/niwrA/mocking-blog
Most developers will have dabbled with test driven development (TDD) at some point in their careers. There are plenty of good reasons for doing so. A very good and common one is proving to the customer or a functional designer that the code adheres to their acceptance criteria. Having acceptance criteria alone often helps making sure that expectations are set correctly and scope-creep is kept at bay. Refactoring code to keep code understandable, efficient and effective is easier when tests tell you early and often that you broke something, and finally, being able to quickly and easily write proper Unit Tests (=tests that test single responsibility of methods and properties) is the best way to verify that you have written SOLID code. Hopefully, you will already be convinced of the value of Unit Tests by the time you read this, because much has already been said on the subject.
Instead, in this blog I want to focus on how to set tests up for maintainability. If you or your team writes a lot of tests, you may find that refactoring takes much longer as a result of having to fix tests that broke because you changed something they depended on. It is unfortunately not too uncommon that a new feature or fixing a bug costs one hour, and then you spend another 7 hours fixing all the tests that broke. Typically, this means that either the tests weren’t properly unit tests, which in turn often means that the code under test wasn’t solid, but sometimes also the test setup itself wasn’t SOLID – it was using other code in the project for instance to test code. A typical example here is that you have an object model in your code for something like an Order, a Product, or anything else, and perhaps you changed something there – maybe you wanted to make something immutable in order to have fewer issues in multi-threaded scenarios, and now your tests can’t set certain properties because they have to be set by the constructor, etc.
Mocking
One of the more common strategies for making sure your test only tests a single criteria is using mocks and stubs. If you have used dependency injection with interfaces, you can very easily setup a test to verify that your code interacts with the dependencies correctly without actually having to run any code in those dependencies. Here, an interface is a contract that code should adhere to. Tests should always only use their own implementation of any contract outside of the responsibility of the function you are going to test.
Fortunately, Mocking frameworks like Moq, NSubstitute and FakeItEasy are a great help here. They have been designed to create implementations of contracts for you automatically that are solely setup to simulate and verify behavior as specified by the contract provided by an interface. In this blog I will use Moq, as it is the most commonly used library in the .NET Framework ecosystem and it clearly separates the mock, setup and verify parts of contract implementation.
Let’s have a look at a few simplified contract examples (we’re ignoring UI for now), that we will later use in our tests:
public interface IOrder
{
IEnumerable<IOrderLine> OrderLines { get; }
IEnumerable<ICoupon> Coupons { get; }
IShippingAddress ShippingAddress { get; }
}
This is a contract for a typical order as a customer has been setting it up. He has selected a few items/products for purchase (OrderLines), perhaps used a few discount options (Coupons) and has provided a shipping address where the products should be delivered after payment. We will focus on the details of the other interfaces like IOrderLine only when they become relevant.
Now, before we move on to having the customer pay for the order, we want to first verify whether the order is valid. A shipping address for instance is required, and we should have at least one orderline.
For this, we create an order validator, which checks if all the requirements are met and gives us the results to report back to the user or allows us to decide to go ahead with the payment process.
public interface IOrderValidator
{
IValidationResults Validate(IOrder order);
}
Now with this combination of designing by contract (using interfaces), and using mocking frameworks, you’ll surprisingly find that you are already able to test your design with actual unit tests! Kudos to anyone who started with unit tests this early in the design process, as I have not been one of you until this blog myself …
A First Test
Here’s an example test (using xUnit):
[Fact]
public void TestInterfaces()
{
var validatorMock = new Mock<IOrderValidator>();
var validationResultsMock = new Mock<IValidationResults>();
var orderMock = new Mock<IOrder>();
validatorMock.Setup(s => s.Validate(orderMock.Object)).Returns(validationResultsMock.Object);
var result = validatorMock.Object.Validate(orderMock.Object);
Assert.False(result.IsValid);
}
Arrange
You can read what happens here, but let’s quickly run through it just in case. First, we ask Moq to give us a mock implementation of the IOrderValidator interface. This, so that we can test its Validate method (or rather, it’s design/signature, in this case).
var validatorMock = new Mock<IOrderValidator>();
Then, we ask Moq to gives us a mock of the IValidationResults, which the Validate function is going to give us back.
var validationResultsMock = new Mock<IValidationResults>();
Finally, we ask Moq to give us a mock object for the order contract (IOrder) which we will pass to the Validate function so that it can validate it and pass back results.
var orderMock = new Mock<IOrder>();
Now that we have our mock implementations for the specified contracts, we can start to specify what kind of behavior we want to simulate with them. In our case, the most basic thing we can and will want to do is have the Validate function pass back the mocked validation results when it is called by passing the mocked order object. We can simply do that like this:
validatorMock.Setup(s => s.Validate(orderMock.Object)).Returns(validationResultsMock.Object);
Note that with Object, we specify that we want to return the actual mocked object, so the implementation of the contact, rather than the mock object that provides all the mock functionality around that mocked object, such as the setup logic (and verify, as we shall see later). Some other mocking frameworks merge these two, but although it takes a bit of getting used to, I like the clarity that separating the verify, setup and object aspects of mocking provides.
Act
So, now that we have the arrange part of testing (the setup), we can continue with calling the actual method of our ‘system under test’ (the technical term for what we are testing), like this:
var result = validatorMock.Object.Validate(orderMock.Object);
It can be a good practice to identify your system under test and its result more explicitly in code like this:
var sut = validatorMock.Object;
var sutResult = sut.Validate(orderMock.Object);
It takes a little getting used to, but it make identifying and validating the structure of a test much easier later on by highlighting the critical parts.
Assert
With the act part of our test out of the way, we can move on to the final part, which is the assert part, i.e. verifying that the system under test behaved as we expect it to. The most basic part of asserting is provided by the testing frameworks themselves (xUnit, NUnit or MSTest). You typically use a simple ‘Assert’ action, like this:
Assert.False(sutResult.IsValid);
As we have not specified any behavior for IsValid to the mocking framework, it will simply assume the default value for the Boolean value, which is false. So this test should pass.
So note again, that weirdly enough, we have not tested any actual code here, just interfaces, but we do see how programming against the interfaces would work and how the interfaces would interact. If your design specifications are clear enough, you can design and test your interfaces in advance before investing in a single line of code, and if the specifications aren’t clear enough or if you are not yet clear about how to implement them, you can easily experiment like this with fairly minimal investment in actually writing code.
Now that we have this minimal setup, we can start influencing some behavior. For instance, we can easily modify the validation result by setting up the mock of the IValidationResult contract to return true for IsValid, by simply adding a line to our arrange part of the test like this:
validationResultsMock.Setup(s => s.IsValid).Returns(true);
Now, our test will return true and in order to pass, the Assert should change from Assert.False to Assert.True as well.
But, if we want to implement more complex behavior, we will see that we will need to start doing a lot of setup in the form of mocks and so on in our tests, and this can make the tests long, harder to read, and harder to maintain. What if we change our mock library? What if we need to mock parts of a library that doesn’t provide interfaces? (if it is one of your own though, fix that asap, shame on you). What if parts of the setup repeat a lot in code but change over time? What if we are working with some really complex object models that we need to validate in all sorts of combinations?
This is where the builder pattern comes in. It can help abstract away and centralise a lot of test logic and make the tests easier to maintain, and read as well.
The Builder Pattern
The builder pattern, which is at least as old as the Design Patterns book by the Gang of Four, today is often implemented in a specific way – first do some configuration with methods that return a new version of the builder object with the new configuration value added, and finally end with calling the Build function that uses all the previously provided configuration values to create a new, configured object.
This works really well with setting up objects for testing, even when using actual objects, but also very much so when using mocks. Let’s look at the end result first this time, and hopefully you can immediately see the improvement in readability a builder provides for test setup:
[Fact]
public void CanValidateOrder()
{
var order = new OrderBuilder()
.Build();
var validator = new ValidatorBuilder()
.Build();
var result = validator.Validate(order);
Assert.True(result.IsValid);
}
In this setup, we don’t actually know anything specific about how we create our test objects. We don’t know anything about their dependencies or specifics, if they used real objects or mocks, or what mocking framework. Instead, we just get our validator and our order, and we can execute our test. Now we can add some configuration to our order to make things a bit more interesting easily like this:
var order = new OrderBuilder()
.WithOrderLine()
.WithShippingAddress()
.Build();
This is the kind of code that you can probably show to a business stakeholder, product owner or functional proxy and they will still understand what is happening in the following test:
[Fact]
public void CanValidateOrder_WithOrderLine_AndShippingAddress()
{
var order = new OrderBuilder()
.WithOrderLine()
.WithShippingAddress()
.Build();
var validator = new ValidatorBuilder()
.Build();
var result = validator.Validate(order);
Assert.True(result.IsValid);
}
This pretty clearly says, whether it is correct or not in reality, that if you build an order with an orderline and a shipping address, the validator will should, according to this test, consider the order valid.
Again, if you use your Builder pattern in combination with a Mock, you can get very precise behavior from bits of code that your test is dependent on, but not actually needs to test. You can in some cases match the actual acceptance criteria for a User Story with tests like these very closely, and again do so even before any actual code has been written, again helping verifying a design.
Building the Builder
So how do you make a builder, in this case for creating a mocked order?
public class OrderBuilder
{
Mock<IOrder> _mock = new Mock<IOrder>();
IList<IOrderLine> _orderlines = new List<IOrderLine>();
public IOrder Build()
{
_mock.Setup(s => s.OrderLines).Returns(_orderlines);
return _mock.Object;
}
public OrderBuilder WithOrderLine()
{
_orderlines.Add(new Mock<IOrderLine>().Object);
return this;
}
}
This is the most basic implementation of the builder. As you can see, it has one Build method that returns the mocked order object. The mock we created for that we simply created as a private variable that we instantiated immediately. So the very core of the builder is actually just this:
public class OrderBuilder
{
Mock<IOrder> _mock = new Mock<IOrder>();
public IOrder Build()
{
return _mock.Object;
}
}
And nothing more. It just returns a mocked object for IOrder using Moq. But this is still valuable – should we want to change mocking frameworks to FakeItEasy or want to use our own objects, this is the only place where we have to change our test code.
The next bit of interest is the WithOrderLine method. This simply returns the object itself (return this), but with an orderline added to its orderlines collection. This collection is then set as the mocked result of the OrderLines property of the mocked order when we call the Build() method. Note that this setup also means you can do this:
var order = new OrderBuilder()
.WithOrderLine()
.WithOrderLine()
.WithOrderLine()
.Build();
… to simply create an order with three orderlines.
Now there is one more bit to draw attention to. In WithOrderLine, we simply created a new mock. But we can do better than that:
public OrderBuilder WithOrderLine()
{
_orderlines.Add(new OrderLineBuilder().Build());
return this;
}
We now use a separate orderlinebuilder instead, so that we again isolate that part of the building process too. Perhaps it needs default properties we need to check for later, or needs a different mocking process as well – we can expect to need various different orderlines for our tests, like lines with too high a quantity or value perhaps. So it is a small effort to prepare for this by adding:
public class OrderLineBuilder
{
Mock<IOrderLine> OrderlineMock { get; set; } = new Mock<IOrderLine>();
public IOrderLine Build()
{
return OrderlineMock.Object;
}
}
The advantages are hopefully clear. As the software gets more requirements and becomes more complex, your builders don’t necessarily – you will just keep adding builders for coupons and so on and use them in the other builders where relevant, and this helps you to both test the individual objects. And don’t be afraid to create new builders even if they create the same object, if you clearly have a different purpose, like for the validation part which has different test requirements from say calculating the total amount that the customer needs to pay.
Let’s add some meat
That’s enough pure mocking for now - let’s use what we’ve learnt, validate a real implementation of a validator against mock orders that we create using the builder pattern.
Here is some actual validation logic:
public class ValidationResults : IValidationResults
{
public bool IsValid => ValidationErrors.Count() == 0;
public ICollection<IValidationError> ValidationErrors { get; } = new List<IValidationError>();
public void AddError(ValidationErrorTypes type, string message)
{
var error = new ValidationError(type, message);
ValidationErrors.Add(error);
}
}
internal class ValidationError: IValidationError
{
private ValidationErrorTypes type = ValidationErrorTypes.NotSet;
private string message = "";
public ValidationError(ValidationErrorTypes type, string message)
{
this.type = type;
this.message = message;
}
public string Message { get { return message; } }
public ValidationErrorTypes Type { get { return type; } }
}
public class OrderValidator : IOrderValidator
{
public IValidationResults Validate(IOrder order)
{
var validationResults = new ValidationResults();
MustHaveOrderlines(order, validationResults);
return validationResults;
}
private void MustHaveOrderlines(IOrder order, IValidationResults results)
{
if(order.OrderLines.Count() < 1)
{
results.AddError(ValidationErrorTypes.OrderlinesRequired, "Must have at least one orderline.");
}
}
}
Hopefully the code is written well enough so that it speaks for itself, as it should. Basically in the above we implemented that we validate an order against conditions, currently only a minimum number of orderlines. If a condition fails, we add an error to the list of errors on the validation results, and IsValid is a calculated result that gives false if there are one or more validation errors.
So, a first set of (two) tests that validates this functionality could look like this:
[Fact]
public void OrderWithOutOrderLine_IsNotValid()
{
var emptyOrder = new OrderBuilder()
.Build();
var sut = new OrderValidator();
var sutInvalidResult = sut.Validate(emptyOrder);
Assert.False(sutInvalidResult.IsValid);
Assert.Single(sutInvalidResult.ValidationErrors);
Assert.Equal(ValidationErrorTypes.OrderlinesRequired, sutInvalidResult.ValidationErrors.First().Type);
}
[Fact]
public void OrderMustHaveOneOrderLine()
{
var validOrder = new OrderBuilder()
.WithOrderLine()
.Build();
var sut = new OrderValidator();
var sutValidResult = sut.Validate(validOrder);
Assert.True(sutValidResult.IsValid);
}
These two tests cover all the important parts. First we create an empty order, and check that this fails validation, and with the proper error message.
Then we create an order with one orderline, and check that it is now considered valid. So that’s a good start.
But now we will make some changes and see how this affects our existing tests. First, we will add verifying that the order has a ShippingAddress. Let’s do this test-first and create a unit test that verifies the new expected behavior, and see that it fails before we actually implement the necessary business logic.
The most basic implementation of such a test given what we already have would seem to be this:
[Fact]
public void OrderWithOutShippingAddress_IsNotValid()
{
var emptyOrder = new OrderBuilder()
.WithOrderLine()
.Build();
var sut = new OrderValidator();
var sutInvalidResult = sut.Validate(emptyOrder);
Assert.False(sutInvalidResult.IsValid);
Assert.Single(sutInvalidResult.ValidationErrors);
Assert.Equal(ValidationErrorTypes.ShippingAddressRequired, sutInvalidResult.ValidationErrors.First().Type);
}
We will now create an order with one orderline, but expect it to fail because the shipping address is missing. But already here we have a problem. What if the order doesn’t have a shipping address and also doesn’t have an orderline? Now we cannot test these two validation errors in isolation anymore – the test we created for testing if the order validation fails without orderlines could now fail first because the shipping address is missing.
We need to change our tests to either look for the correct error in the list of errors by type, and then our test setup could be relatively robust again. But we will miss a few things – can we now check that a missing orderline doesn’t result in more than one validation error? The number of validation errors in our test will just increase each time we add a check to the Validate code now for all tests that we started with an empty order. So we need to keep adding all the required parts to all those tests.
In terms of maintenance, this is not what we want. Instead, perhaps we can setup our tests to always start with a valid order, and then make only that part invalid that we want to test for. So, we can create something we might call the ‘ValidOrderBuilder’, which we update whenever we have a new requirement. We can make sure this keeps working as expected by giving that its own IsValid test like this:
[Fact]
public void ValidOrder_IsValid()
{
var order = new Tests.ValidOrderBuilder().Build();
var sut = new OrderValidator();
var sutResult = sut.Validate(order);
Assert.True(sutResult.IsValid);
Assert.Empty(sutResult.ValidationErrors);
}
And now, all tests for validation errors can use by calling it with a negation, using Without instead of With, like this:
[Fact]
public void ValidOrder_WhenNoOrderLines_InvalidatesCorrectly()
{
var order = new Tests.ValidOrderBuilder()
.WithoutOrderLines()
.Build();
var sut = new OrderValidator();
var sutResult = sut.Validate(order);
Assert.False(sutResult.IsValid);
Assert.Single(sutResult.ValidationErrors);
Assert.Equal(ValidationErrorTypes.OrderlinesRequired, sutResult.ValidationErrors.First().Type);
}
Now this is much easier to maintain and much more accurate to test. Our new test for the ShippingAddress would simply look like this:
[Fact]
public void ValidOrder_WhenNoShippingAddress_InvalidatesCorrectly()
{
var order = new Tests.ValidOrderBuilder()
.WithoutShippingAddress()
.Build();
var sut = new OrderValidator();
var sutResult = sut.Validate(order);
Assert.False(sutResult.IsValid);
Assert.Single(sutResult.ValidationErrors);
Assert.Equal(ValidationErrorTypes.ShippingAddressRequired, sutResult.ValidationErrors.First().Type);
}
Much better and easier to maintain. So what does that look like in the builder? The difference isn’t that big. Basically, we put what we had in our WithOrderLine and WithShippingAddress in the builder constructor instead:
public ValidOrderBuilder()
{
_shippingAddressMock = new Mock<IShippingAddress>();
_orderlines.Add(new OrderLineBuilder().Build());
}
So now, basically, the order that we return is already valid. Each time that a new requirement is added we can add that here, and the test we created earlier makes sure that this isn’t easily overlooked by developers.
Then, in the Without versions, we clear these values like this:
public ValidOrderBuilder WithoutOrderLines()
{
_orderlines.Clear();
return this;
}
public ValidOrderBuilder WithoutShippingAddress()
{
_shippingAddressMock = null;
return this;
}
Easy enough. But the important part to remember here is that your Builder doesn’t have to be a simple recreation of creating a basic objects and adding children – your with or without methods should be whatever is most useful for your tests.
For example, perhaps it makes more sense at some point to create specific types of orderlines in the test and add them from there, to see if the validation or perhaps a delivery costs calculator picks up on that special situation – for instance products with very different delivery times should lead to the customer specifying if he or she prefers to pay more or wait for both products.
Here you would have two choices: add a generic function “WithOrderLine(IOrderLine orderLine) which tests can then use to add specific lines with specific values set, or you could create a new method called WithTwoOrderlinesWithDifferentDeliveryTimes, or create two methods called WithOrderLineSameDayDelivery and WithOrderLineDeliveryInAWeek.
Whatever works best for your scenario is the best solution, but err on the side of whatever is the most readable in your tests. That which most clearly shows the intent alone is typically also easiest to maintain.
So, with that out of the way, let’s implement the new requirement. We actually need to do very little other than add this:
private void MustHaveShippingAddress(IOrder order, IValidationResults results)
{
if (order.ShippingAddress == null)
{
results.AddError(ValidationErrorTypes.ShippingAddressRequired, "Must have a shipping address.");
}
}
And call that function right after MustHaveOrderlines in our Validate method.
Refactor
So that all hopefully looks pretty good on paper and I think many advantages already speak for themselves. But let’s look at a tiny bit of refactoring and seeing if this setup has helped us here with that too. First a small one, that could nevertheless have had a lot of impact, both on the safety of the code and on how many tests would have needed to be changed.
In the IValidationResults, the ValidationErrors property is get only. This is good as we don’t want it overwritten – but being a collection, someone could actually still add items to the list of ValidationErrors from the outside or clear the list or whatever. That’s not what we want, so we need to use the IReadOnlyCollection instead. As this is something in an interface, it could have a bit impact. But fortunately, it doesn’t. Apart from changing the interface and the actual implementation:
IReadOnlyCollection<IValidationError> IValidationResults.ValidationErrors
{
get {
return (IReadOnlyCollection<IValidationError>)_validationErrors;
}
}
… only this single property in our ValidationResultsBuilder needed to be changed for our tests to become green again:
IReadOnlyCollection<IValidationError> _validationErrors = new List<IValidationError>();
And if we do the same with the OrderLines property on the Order, we only needed to add a cast in the setup of our builder:
_mock.Setup(s => s.OrderLines).Returns((IReadOnlyCollection<IOrderLine>)_orderlines);
SutBuilder
And what if we now needed to start injecting dependencies into the Validator to be able to make some rest calls to validate some other stuff from a remote service? Just add a builder that creates your 'system under test', in this case your Validator, and then in that builder mock the dependencies, and mock some of the setup (e.g. external service rejects customer name, etc.). The blog is already getting too long to go into much detail here, but hopefully you are starting to figure out the pattern enough to figure this out on your own, and if not, post in the comments.
Conclusion
The combination of interfaces, mocks and builders greatly helps making tests even more a help than a hindrance when refactoring code, and greatly contributes to the readability and ease of setup when creating new tests as well. The tests look more like requirements, and they are therefore also easier to write test-first making your tests even more reliable and helpful while developing – after all, if you develop test-first, not only have you verified that your test failed before you started, but you can typically also debug your new method/code right from the test, which is definitely the most efficient way to develop. Hopefully you will be able to apply this in your own projects soon and be as happy about discovering this way of testing as I have been.
And use interfaces. Always. For everything.