DEV Community

Cover image for Don't Restrict Yourself to Mocks: Test your Components Running Together
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

Don't Restrict Yourself to Mocks: Test your Components Running Together

So far, we’ve been looking at writing unit tests. These are great at checking that the individual parts of our app are working correctly. But sometimes we want to zoom out a little; we want to be sure that multiple components work when connected. We want something bigger than a unit test, but not encompassing the whole app – maybe because it hasn’t been completed yet, maybe because we want to stay focussed within a specific area.

Well, it turns out there’s a test for that.

In an integration test, we chain multiple components together and check that everything goes smoothly. If we cast our minds back, we were writing a small app to calculate the hypotenuse of a triangle. There were three steps in the calculation, and we’ve already covered how we might go about testing the first two:

  1. Squaring a number. We did this with two different values.

  2. Finding the sum of the two squares.

One step remains – finding the square root of the total.

The Final Part of the Calculation

As we’ve already created a CalculationService, we can add a new method to it that joins everything together. Let’s start by adding a new test:

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

    var multiplicationService = new MultiplicationService();
    var service = new CalculationService(multiplicationService);

    // Act

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

    // Assert

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

We’ve used a real instance of MultiplicationService in this test. This contrasts with our previous tests where we created testing mocks using the Moq library. With the test out of the way, we can now add a new method to CalculationService to implement the missing functionality.

public double Hypotenuse(int a, int b)
{
    var sumOfSquares = SumOfSquares(a, b);
    var hypotenuse = Math.Sqrt(sumOfSquares);
    return hypotenuse;
}
Enter fullscreen mode Exit fullscreen mode

Note that we’ve used an already-existing C# library method (Math.Sqrt) to find the square root. As it’s not something that we’ve written ourselves, there’s no way to mock out that method – nor should we. We can assume that the C# library maintainers have already tested it. And even if it did contain bugs, there’d be no direct way for us to fix it.

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

The Visual Studio Test Explorer showing all tests have passed

Image 1: The CalculationServiceCanCalculateHypotenuse test has passed

Integration vs Unit Tests

In a unit test, we typically isolate the system under test (SUT) by substituting all its dependencies. The previous articles in this series did this by mocking; there are other types of test substitute, but we won’t go into those now. Isolating the SUT helps to narrow our search if things go wrong, as there are no other components/dependencies involved.

In an integration test, we use actual instances of components to check that a process has the expected outcome. By testing the output of more than one component, we can check whether those components produce the desired outcome when working together. However, if the test fails, we only know that something in the chain is misbehaving. We have less accuracy locating the point of failure at a glance.

As such, one type of test isn’t ‘better’ than the other – they complement each other. Unit tests ensure that individual components function correctly, while integration tests check that chains of components generate the right outcome. As integration tests involve more than one component, they might take longer to run.

It’s important to note that an integration test doesn’t have to include all the components of an entire workflow. It’s also possible (and often a good idea) to test smaller chains of components too. By doing so, individual tests can take less time to run. Having smaller component chains also helps to localise areas to investigate should a test fail. In tests where we don’t test an entire workflow, we can use substitutes such as mocks to replace dependencies at either end of the component chain where necessary.

Summary

Unit tests have a narrow focus. Integration tests allow you to zoom out. This lets you test parts of your app running together as they would when deployed. While running multiple components allows you to test a workflow (or a part of it), there may be some disadvantages. Depending on the components involved, tests could take longer to run. Also, it’s potentially less clear where the problem lies if a test fails, as there are multiple components involved.

As such, it’s a good idea to have both unit and integration tests in your project. By taking a multi-layered approach, you can benefit from the best of both worlds.


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).

Top comments (0)