Mock – my favourite mock engine

When I try to unit test class one of the most important things is to eliminate all dependencies and replace them with proper mocks. Such mocks allow me to fully control inputs that object will access and verify that proper methods have been called.

Recently I’ve written one business logic class that manages SPA (Single Page Application) and uses four different repositories from which it pulls data and presents them as Web API.

Model is implementing an interface (which would make testing of controllers easier) which looks like this:

public interface IModel
{
    Task CreateClient(string clientName);
    Task CreateStrategyForClient(Strategy strategy);
    Task GetClientNames();
    Task GetStrategiesForClient(string clientName);
    Task GetStrawmenForClient(string clientName);
    Task UpdateStrategy(Strategy strategy);
    Task UpdateStrawman(Strawman strawman);
}

Hopefully thanks to clean code principles it’s clear enough what each of those methods is doing. In production code, dependency injection is managed by Autofac resolver, but in tests, I prefer to use my own mock objects. The signature of the constructor for Model class looks like this:

public Model(
    IClientRepository clientRepository,
    IStrategyRepository strategyRepository,
    IStrawmanRepository strawmanRepository,
    IStaticDataService staticDataFetcher)

First setup and test

It’s time to write our test “framework” that will help us perform all the tests. I will start by initializing client mock repository with three clients generated by code. Let’s create private field

private Mock clientRepository;

And add the constructor that will be called before each executed test:

public ModelTests() {
    var clients = Enumerable.Range(1, 3)
                .Select(i => new Client()
                    {
                        Id = Guid.NewGuid(),
                        Name = $"Client_{i}"
                    })
                .ToList();
    this.clientRepository = new Mock();
    this.clientRepository.Setup(cr => cr.GetClientNames())
        .ReturnsAsync(clients.Select(c => c.Name).ToList);
}

We already have done part of clientRepository setup when GetClientNames method will return all three clientNames if called.

Time to write our first actual test

[Fact]
public async Task GetClientNames_ReturnsMockEntries()
{
    // Arrange
    var model = new Model(this.clientRepository.Object, null, null, null);

    // Act
    var result = await model.GetClientNames();

    // Assert
    Assert.NotNull(result);
    Assert.Equal(3, result.Count);
    this.clientRepository.Verify(cr => cr.GetClientNames(), Times.Once);
}
ncrunch
Usage of NCrunch (of which I’ve already written) gives us instant feedback about newly written test pass/fail status.

What is noteworthy here is that our test doesn’t depend on anything that is not in our testing file. It doesn’t matter if actual ClientRepository is remote Web API, SQL Database or just a JSON file on a disk. Using interface and Mock we completely abstracted that away and we can focus on logic in a just single class.

Second test – a bit more complex

The second method that I would want to test is GetStrategiesForClient, which requires one parameter – client name. It then queries ClientRepostirory for this name, obtains clientId and queries StrategyRepository for all strategies that have the corresponding client Id.

First of all, we need to setup properly method in IClientRepository used to retrieve user. To TestSetup method I added following code:

var usernames = clients.Select(c => c.Name);

this.clientRepository.Setup(cr => cr.GetClientByName(It.IsIn(usernames)))
                     .Returns((string name) =>
                         Task.FromResult(clients.Single(c => c.Name == name)));
this.clientRepository.Setup(cr => cr.GetClientByName(It.IsNotIn(usernames)))
                     .ThrowsAsync(new ArgumentException());

A very similar thing should be done with IStrategyRepository. Depending on how much accuracy is required we could use an anonymous function to create required objects on the fly:

this.strategyRepostory = new Mock();
this.strategyRepostory.Setup(sr => sr.GetStrategiesForClient(It.IsAny()))
    .Returns(
        (Guid clientId) => Task.FromResult(
            (IReadOnlyCollection)
                Enumerable.Range(0, 3)
                    .Select(i => new Strategy {
                        Name = $"Strategy_{i}",
                        Id = Guid.NewGuid(),
                        ClientId = clientId })
                    .ToList()
                    .AsReadOnly()));

And thanks to mocking like this we can now follow it up with tests:

[Fact]
public async Task GetStrategiesForClient_ValidClient_ReturnsStrategies()
{
    // Arrange
    var model = new Model(this.clientRepository.Object, this.strategyRepostory.Object, null, null);

    // Act
    var result = await model.GetStrategiesForClient("Client_1");

    // Assert
    Assert.NotNull(result);
    Assert.Equal(3, result.Count);
    this.clientRepository.Verify(cr => cr.GetClientByName(It.IsAny()), Times.Once);
    this.strategyRepostory.Verify(sr => sr.GetStrategiesForClient(It.IsAny()), Times.Once);
}

[Fact]
public async Task GetStrategiesForClient_InvalidClient_ThrowsException()
{
    // Arrange
    var model = new Model(this.clientRepository.Object, this.strategyRepostory.Object, null, null);

    // Act
    await Assert.ThrowsAsync(async () => await model.GetStrategiesForClient("Client_4"));

    // Assert<span>
    this.clientRepository.Verify(cr =&gt; cr.GetClientByName(It.IsAny()), Times.Once);
    this.strategyRepostory.Verify(sr =&gt; sr.GetStrategiesForClient(It.IsAny()), Times.Never);
}

Conclusion

The Mock library can help you tremendously with the testing behaviour of your class, given that few requirements are met:

  • You properly use dependency injection and rely on interfaces to be feed to your constructors
  • You don’t overdo complexity of mocks that you use.
  • You don’t use static variables, repositories so that your tests are thread safe

Happy testing 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s