NOTE: This is not my idea, this is Brad Wilson’s, but it’s so useful, and as far as I know, so little known that I had to write my own take on explaining it. Here’s the link to Brad Wilson’s post about it. It’s called Testable Object Pattern.
When practising test driven development, or when adding unit tests in general, we often have to add or remove dependencies from a class. Usually those dependencies are parameters in the class’ constructor, so each time you add or remove one, the constructor’s signature changes.
Let’s look at an example.
Here’s how an hypothetical class named ListService
‘s dependencies and constructor:
...
private readonly IUrlCreator _urlCreator;
private readonly IResponseParser _parser;
private readonly IListApiCaller _caller;
private readonly IRequestBodyCreator _bodyCreator;
public ListService(IUrlCreator urlCreator, IListApiCaller caller, IResponseParser parser, IRequestBodyCreator bodyCreator)
{
_urlCreator = urlCreator;
_caller = caller;
_parser = parser;
_bodyCreator = bodyCreator;
}
...
Here’s the interface it implements:
public interface IListService
{
IEnumerable<ListDescription> GetLists(IUserLogin userLogin);
ListDescription Get(IUserLogin userLogin, Guid id);
ListDescription Create(IUserLogin userLogin, string name);
void Update(IUserLogin userLogin, Guid id, string name);
void Delete(IUserLogin userLogin, Guid id);
}
It’s purpose is to allow a user to create, update and delete lists and the implementation does this using a REST Api.
Often unit tests won’t depend on all the class’ dependencies, a test might not even depend on any. Frequently null
is passed as an argument instead of an instance of the dependency when that dependency is not needed in a test, for example:
[Test]
[ExpectedException(typeof(ArgumentException))]
public void Create_ValidLoginEmptyName_ThrowsArgumentException()
{
var listService = new ListService(null, null, null, null);
var userLogin = new UserLogin("username", "token", expiryDate: DateTime.Now.AddDays(1));
_listService.Create(userLogin, name: string.Empty);
}
In this example no dependency will be needed because this test just checks the user is not trying to create a list with an empty name, and it should throw an exception before any dependency is used. In case you are wondering the unit-testing framework is NUnit and the isolation framework is Moq.
Now, say that you performed some re-factoring and you added and/or removed some dependencies. Each test needs to be updated because the constructor’s signature changes. Even the test in the example, even though it does not really involve any dependencies.
A solution for this is to create a derived class of the class you want to test, let’s call it TestableListService
that exposes all the class’ dependencies as mocks. I know, I know, you are probably cringing right now thinking, oh there’s only supposed to be one mock per test, all the rest should stubs. And that’s true, I’ve actually written about that. Thankfully Moq is very relaxed about that, there’s only one type of object, a Mock<T>
, that can act as either a mock or a stub. It’s up to you to do the right thing.
So, you inherit from the class you want to test, and for each dependency you create a property with a mock for it. If you have a dependency on IResponseParser
, add a public property Mock<IResponseParser>
. Do this for every dependency in the class under test.
Next, make the constructor private (you don’t want to deal with building the object, that is the source of the pain). The parameters for the constructor are Mock<T>
for each dependency. Use the base
keyword to initialize the real dependencies in the base class (this will become obvious with an example). In the constructor’s body set the properties that hold the Mock<T>
for each of the dependencies.
Finally, expose a public static method that will be responsible for creating the testable object, and use that.
So for our ListService
that would be something like this:
class TestableListService : ListService
{
public Mock<IUrlCreator> UrlCreatorMock { get; private set; }
public Mock<IListApiCaller> ApiCallerMock { get; private set; }
public Mock<IResponseParser> ResponseParserMock { get; private set; }
public Mock<IRequestBodyCreator> BodyCreatorMock { get; private set; }
private TestableListService(Mock<IUrlCreator> urlCreator, Mock<IListApiCaller> apiCaller, Mock<IResponseParser> parser, Mock<IRequestBodyCreator> bodyCreator)
: base(urlCreator.Object, apiCaller.Object, parser.Object, bodyCreator.Object)
{
UrlCreatorMock = urlCreator;
ApiCallerMock = apiCaller;
ResponseParserMock = parser;
BodyCreatorMock = bodyCreator;
}
public static TestableListService Create()
{
return new TestableListService(new Mock<IUrlCreator>(), new Mock<IListApiCaller>(), new Mock<IResponseParser>(), new Mock<IRequestBodyCreator>());
}
}
A note about what’s going on if you’ve never used Moq. In Moq you create mocks by creating a new instance of Mock<T>
, you can then configure it using the Setup
method, and when you are done you can get the instance of the configured class that implements the interface (the actual mock or stub) by calling .Object
.
We can now use this version of our class under test which won’t suffer from the previous problem where we would have to update every test every time a dependency is added or removed.
Here’s an example of a test that checks if the right exception is thrown if the user tries to delete a list that does not exist:
[Test]
[ExpectedException(typeof(ListNotFoundException), ExpectedMessage = "List not found")]
public void Delete_ValidLoginUnknownList_ThrowsListNotFoundException()
{
var listService = TestableListService.Create();
var userLogin = new UserLogin("username", "token", expiryDate: DateTime.Now.AddDays(1));
var listId = Guid.NewGuid();
listService.UrlCreatorMock.Setup(s => s.CreateDeleteUrl(listId)).Returns("deleteurl");
var apiResponse = new Mock<IOAuthApiCallResponse>();
apiResponse.Setup(r => r.HasError).Returns(true);
apiResponse.Setup(r => r.HttpStatusCode).Returns(404);
apiResponse.Setup(r => r.HttpStatusDescription).Returns("List not found");
listService.ApiCallerMock.Setup(s => s.Delete(userLogin, "deleteurl")).Returns(apiResponse.Object);
listService.Delete(userLogin, listId);
}