Isolate your Components in Tests: How to Mock your Dependencies

ant_f_dev

Anthony Fung

Posted on June 7, 2023

Isolate your Components in Tests: How to Mock your Dependencies

We previously mentioned that unit tests act as basic checks for our app’s components. We also looked at how to write tests where we passed values into a calculation and compared the outputs with expected results. In these tests, we fed our sample inputs directly into a method that took a number, squared it, and then returned the result. No additional components were needed in the process.

However, things aren’t always that straightforward.

Sometimes, the logic in our services will rely on more than one component. In fact, it’s often a good idea to separate responsibilities when designing software modules. By keeping a module’s logic focussed to a specific area, it has a single identifiable purpose. This in turn means that we can reuse that module wherever it’s needed without having to bring in other features and capabilities that may not be immediately relevant. Furthermore, we can name the module more meaningfully, potentially making the codebase easier to understand.

In a unit test, we want to test a single component only – not a workflow. If the subject under test has dependencies, we deliberately want to exclude them from the test. That way, we’ll know exactly where to focus our debugging efforts if there’s ever a problem. One way to do this is Mocking.

The Problem so Far

We started the series by wanting to be able to calculate the hypotenuse of a triangle. To do so, we would need to do three things:

  1. Multiply a number by itself to find its square. We would do this for two numbers.

  2. Calculate the sum of the two values obtained from step 1.

  3. Find the square root of the result from step 2.

As part of completing step 1, we created a MultiplicationService that could square any given number. Focussing on step 2, let’s create a new CalculationService that:

  • Accepts two separate numbers as input values.

  • Squares them in the MultiplicationService.

  • Adds the two results together.

The code for this might look like the following:

public class CalculationService
{
    private readonly IMultiplicationService _multiplicationService;

    public CalculationService(IMultiplicationService multiplicationService)
    {
        _multiplicationService = multiplicationService;
    }

    public int SumOfSquares(int a, int b)
    {
        int aSquared = _multiplicationService.Square(a);
        int bSquared = _multiplicationService.Square(b);
        return aSquared + bSquared;
    }
}
Enter fullscreen mode Exit fullscreen mode

Writing the Test

We can see that SumOfSquares makes use of Square from MultiplicationService. To ensure that we only test logic within the SumOfSquares method, we can use a library called Moq to mock out MultiplicationService in our C# unit tests. To do this we first create an interface for MultiplicationService that contains all of its public methods:

public interface IMultiplicationService
{
    int Square(int number);
}
Enter fullscreen mode Exit fullscreen mode

Once MultiplicationService implements it, we can write our unit test. A first attempt might look like this:

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

    var multiplicationService = Mock.Of<IMultiplicationService>();
    var service = new CalculationService(multiplicationService);

    // Act

    var result = service.SumOfSquares(3, 4);

    // Assert

    Assert.That(result, Is.EqualTo(25));
}
Enter fullscreen mode Exit fullscreen mode

However, we’ll see that the test fails if we run it:

Expected: 25
But was:  0
Enter fullscreen mode Exit fullscreen mode

Why did this fail?

If we look at our test, we can see that we provided a mock of IMultiplicationService. However, we didn’t configure it to return any values. We can do this by modifying the declaration slightly:

var multiplicationService = Mock.Of<IMultiplicationService>(s =>
    s.Square(3) == 9 &&
    s.Square(4) == 16);
Enter fullscreen mode Exit fullscreen mode

Here, we’re saying that:

  • When we call Square with 3, we want our mock to return a value 9.

  • When we call Square with 4, we want 16 to be returned.

By doing this, we can be sure that the logic in SumOfSquares is correct, even if the implementation of Square changes such that its results become unreliable. After all, we’re currently writing a unit test: it should only test the logic of a single component, not the overall workflow. After the modification, our test would look like this:

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

    var multiplicationService = Mock.Of<IMultiplicationService>(s =>
        s.Square(3) == 9 &&
        s.Square(4) == 16);

    var service = new CalculationService(multiplicationService);

    // Act

    var result = service.SumOfSquares(3, 4);

    // Assert

    Assert.That(result, Is.EqualTo(25));
}
Enter fullscreen mode Exit fullscreen mode

As shown in Image 1, we should see that our test now passes when we run it.

The Visual Studio Test Explorer showing all tests passed

Image 1: The CalculationServiceCanCalculateSumOfSquares test has passed

Summary

When writing unit tests, you want to test your components in isolation. With components that have dependencies, one way of limiting the scope of logic being tested is by mocking those dependencies. In C#, you can use Moq to do this. When creating a mock, it’s possible to specify its behaviours. In other words, you can configure it to return a specific value whenever it is given a certain input value.

By doing this, you can focus each test on a specific area of the code. With enough unit tests, you’ll be able to build up an overview of your app. And if something goes wrong, your hard work will pay off because your tests will give you a good idea of where the problem lies.


Thanks for reading!

This series aims to cover the basics of software testing, giving you more confidence in your code and how you work with it.

If you found this article useful, please consider signing up for my weekly newsletter for more articles like this delivered straight to your inbox (link goes to Substack).

💖 💪 🙅 🚩
ant_f_dev
Anthony Fung

Posted on June 7, 2023

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

Sign up to receive the latest update from our blog.

Related