Isolate your Components in Tests: How to Mock your Dependencies
Hi Friends.
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:
Multiply a number by itself to find its square. We would do this for two numbers.
Calculate the sum of the two values obtained from step 1.
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;
}
}
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);
}
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));
}
However, we’ll see that the test fails if we run it:
Expected: 25
But was: 0
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);
Here, we’re saying that:
When we call
Square
with3
, we want our mock to return a value9
.When we call
Square
with4
, we want16
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));
}
As shown in Image 1, we should see that our test now passes when we run it.
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.