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.
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 (2)
This was an excellent article that highlights the importance of isolating components in tests and mocking dependencies. I really appreciate how you've broken down the process step-by-step, making it easy to follow for beginners and experienced developers alike. Moq seems like a potent tool for crafting isolated tests in C#. Your focus on showing practical examples and offering clear explanations is incredibly valuable.
I have a quick question: When mocking complex dependencies that have many methods and properties, do you have any recommendations for effectively managing these mocks and ensuring that all required behaviors are covered?
Keep up the great work, and I'm looking forward to your future articles on software testing and best practices in C#. Cheers!
Thanks for reading!
Great question. There are two things that you can do.
The first can be helpful sometimes, and is to set a mock’s
DefaultValue
toDefaultValue.Mock
. This will return a mock where possible instead ofnull
if something hasn’t been set up. More details can be found hereThe second is something that I do quite regularly. Instead of
new
ing up a mock in the test, I create a factory method to do it for me. The most basic case would look like the example below.If a complex mock of e.g.
IDependency2
is needed in the majority of cases, it can be set up in the factory method. This means the setup doesn't have to be repeated each time.The factory also accepts optional parameters, meaning that if a custom mock is needed for one test, it can be created in that test and passed to the factory.
Using a factory means that each mock is a unique instance, so tests can still be run in parallel if required.
It's quite hard to explain without making this too long (will probably cover it in a future article), but I hope this gives a good idea of what I'm trying to convey.