How to Easily Create New Mock Instances in Unit Tests
Hi Friends.
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)
{
}
}
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
}
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
}
}
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
}
}
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
}
}
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.
Bonus Developer Tip
There’s a keyboard shortcut to launch Task Manager in Windows. By pressing Ctrl+Shift+Esc
, you can access it in a single step without needing to use any menus. This key combination might seem tricky, but holding down both Ctrl
and Shift
with your thumb lets you reach for Esc
with one of your fingers on the same hand.
These tips are exclusively for my Substack subscribers and readers, as a thank-you for being part of this journey. Some may be expanded into fuller articles in future, depending on their depth.
But you get to see them here, first.