How to Easily Create New Mock Instances in Unit Tests

ant_f_dev

Anthony Fung

Posted on September 27, 2023

How to Easily Create New Mock Instances in Unit Tests

We’re often told we should keep components light and focussed when building software. And with good reason too. Modules with a dedicated purpose are easier to work with; reuse; and test. However, one drawback is we need more of them to capture the same overall amount of logic.

Services can become more complex to instantiate when their dependency counts increase, as this means they’ll have more constructor parameters. This isn’t a problem when we use Inversion of Control frameworks and take advantage of Dependency Injection. However, we typically still need to provide the arguments explicitly – whether in the form of the real thing, a mock, or another type of substitute – in our tests.

In this article, we’ll look at how we can simplify the process of supplying these dependencies when writing tests.

A Typical Approach to Writing Tests

Let’s assume we have a service called MyService and want to test it; to help us focus, we won’t include any methods, properties, or logic in our example. MyService has a constructor that takes two parameters: an IDependency1 and an IDependency2.

public class MyService
{
    public MyService(
        IDependency1 dependency1,
        IDependency2 dependency2)
    {
    }
}
Enter fullscreen mode Exit fullscreen mode

In tests we’ve written so far, we’ve instantiated the objects we want tested directly in the Arrange sections: they’d look similar in style to the following example:

[Test]
public void MyTest()
{
    // Arrange

    var service = new MyService(
        Mock.Of<IDependency1>(),
        Mock.Of<IDependency2>());

    // Act, Assert
}
Enter fullscreen mode Exit fullscreen mode

This is a good approach when creating objects that have few (or no) dependencies. It strikes a good balance between the size of the test and keeping things simple: there isn’t too much code involved, and the lack of indirection means we can see all the logic without having to scroll or jump around on-screen while reading it.

In the previous example, we created and passed two default mocks as dependencies of MyService. But real-world systems are sometimes more complex than this, and it’s common for Arrange sections to require more code. This might be because:

  • We need to set up various members (methods and properties) of the mocks before passing them in.

  • We have more than two dependencies, especially if we break down systems while following the Single Responsibility Principle.

When repeated across many tests, we can easily end up with lots of duplicated code. To make things worse, we’ll have to make as many changes as there are copies if any of the dependencies change.

Taking a Dry-er Approach

When setup code is identical, it’s tempting to follow the DRY principle (Don’t Repeat Yourself). One approach I’ve seen extracts the SUT (system under test) from the individual-test level and moves it to the test-fixture (i.e. class) level. As shown in the following example, this solves the problem of duplicating code while freeing up the Arrange sections of tests.

public class MyServiceTests
{
    private MyService _service = new MyService(
        Mock.Of<IDependency1>(),
        Mock.Of<IDependency2>());

    [Test]
    public void MyTest()
    {
        // Arrange

        // Act, Assert
    }
}
Enter fullscreen mode Exit fullscreen mode

If any aspect of MyService or its dependencies are stateful, we can reset them in between tests using setup methods; this is recommended when using NUnit, but may not be necessary with other testing frameworks.

While this approach works, I personally tend to not use it for two reasons:

  • I try to avoid using state where possible.

  • Customising an SUT’s dependencies for different tests is no longer an option.

The first point is a style preference: I find having variables as tightly scoped as possible helps when keeping track of things, but I know not everyone shares this view. The second however, is important regardless style: your SUT’s dependencies may need to be configured differently depending on what’s being tested.

Making Things Tweakable

The following example shows how we can address both points by creating a factory method for our SUT. The method takes two parameters, one for each dependency. A dependency will be used if its corresponding argument isn’t null and will fall back to a value defined in the factory otherwise. In this example, this value is a default mock, but it’s possible to add setups if necessary. The parameters have default values of null, so in the simplest case we don’t need to provide any arguments. This helps to keep the test code to a minimum.

public class MyServiceTests
{
    private static MyService CreateMyService(
        IDependency1? dependency1 = null,
        IDependency2? dependency2 = null)
    {
        var service = new MyService(
            dependency1 ?? Mock.Of<IDependency1>(),
            dependency2 ?? Mock.Of<IDependency2>());

        return service;
    }

    [Test]
    public void MyTest()
    {
        // Arrange

        var service = CreateMyService();

        // Act, Assert
    }
}
Enter fullscreen mode Exit fullscreen mode

If a test has requirements uncommon to others in the fixture, this gives us the flexibility to set up one (or more) of our SUT’s dependencies in a bespoke way and pass it in when calling the factory method. If we wanted dependency2 in the preceding example to be a strict mock, we can see how to achieve this in the following code.

public class MyServiceTests
{
    private static MyService CreateMyService(
        IDependency1? dependency1 = null,
        IDependency2? dependency2 = null)
    {
        var service = new MyService(
            dependency1 ?? Mock.Of<IDependency1>(),
            dependency2 ?? Mock.Of<IDependency2>());

        return service;
    }

    [Test]
    public void MyTest()
    {
        // Arrange

        var service = CreateMyService(
            dependency2: new Mock<IDependency2>(
                MockBehavior.Strict).Object);

        // Act, Assert
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

While following the Single Responsibility Principle can have many benefits, it can lead to modules having more dependencies. This is usually most noticeable in tests where you typically call constructors explicitly while manually providing arguments.

One way to keep test code to a minimum is to extract the instantiation of SUTs to the fixture level. While this might make them less flexible for tests with different requirements, you can build on this approach. By using a factory to create testing subjects, you can balance tidiness with functionality, writing code for custom dependencies only in tests where they’re needed.


Thanks for reading!

This article is from my newsletter. If you found it useful, please consider subscribing. You’ll get more articles like this delivered straight to your inbox (once per week), plus bonus developer tips too!

💖 💪 🙅 🚩
ant_f_dev
Anthony Fung

Posted on September 27, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related