Why the need for mock testing?
Unit testing is a good practice by itself. It facilitates the writing of clean, robust code that can be verified in a quick, automated, and isolated way.
But you may face complex boundaries in your system, like server availability or external third-party components, which conflict with your usual unit testing.
In that case, mock testing, ormocking, can help you deal with those limitations. Mocking can allow you to handle dependencies that need to be reduced, isolated, and mimicked, to maintain the testability of your codebase.
So what's a mock and how do I test it? 😀
In object-oriented programming, a mock object, ormock, is a controlled object that mimics a "real" component of your code. When you can't test the real thing, you can test a mock of it instead!
Mocking takes advantage of aspects of OOP, like interfaces and classes, to simulate the behavior of specific dependencies in your code that are difficult to test directly. This ensures that those dependencies can be covered with test cases.
As a practice, a dependency on your code might be identified in your system and then isolated. In real life, they are commonly a restricted/low-access database, a server outside your domain, a license that needs to be used, or a component that is somehow hard to deal with in your code.
By identifying dependencies in your system, you can take advantage of interfaces to abstract, reduce, and replace them in your code. Then, the code in your tests can interact with mocks as if they were real. In fact, that helps you to isolate the code under testing, so it doesn't prevent it from executing fast and repeatedly.
Use the following steps to put mock testing into practice:
Identify and declare your mocks
Mimic the behavior of real components
Assert your mock code
Identify and declare your mocks
You will set up the object in your test code, specifically in the arrange section of your 3A pattern.
Let's go over an example:
Let's say our CalculatorProgram will need maintenance on December 31, 2099, right before the next century. At that time, it should throw an exception to alert the need for maintenance. But how do you test something like that without waiting until the actual date?
public Calculator()
{
if (DateTime.Now.Date.CompareTo(new DateTime(1, 1, 2100, 0, 0, 0)) >= 0)
}
Refactor time! 😁
We can identify a dependency in our code that relies on System.DateTime.Now
to make testing easier by introducing an interface that will handle theDateTime.Now
and use mocks later. The mock can provide a fake value that can be used to test the requirement. Instead of the previous code, we will refactor it to be:
public Calculator(IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.GetNow().CompareTo(new DateTime(1, 1, 2100, 0, 0, 0)) >= 0)
{
throw new InvalidOperationException("Hey, it's time to make some maintenance!");
}
}
Let's see the IDateTimeProvider interface code:
using System;
namespace CalculatorProgram
{
public interface IDateTimeProvider
{
DateTime GetNow();
}
public class DateTimeProvider : IDateTimeProvider
{
public DateTime GetNow() => DateTime.Now;
}
}
Sounds simple, right? Let's make it even easier! There is actually a mocking library available for .NET to help you compose your tests! This library is called Moq.
In our UnitTests.csproj we can introduce Moq as follows:
<ItemGroup>
...
<PackageReference Include="Moq" Version="4.10.1" />
</ItemGroup>
Then we can start using Moq in our project.
Mimic the behavior of real components
You can set up the code that mimics the behavior of the real code, like receiving parameters and return values on method calls. Also, properties can be set. Let's see how our mocks look:
[Fact]
public void Test_MaintenanceDateHit()
{
// 1) Arrange
var mockDateTime = new Moq.Mock<IDateTimeProvider>();
mockDateTime.Setup(mock => mock.GetNow()).Returns(() => new DateTime(2100, 1, 1, 0, 0, 0));
var exception = Assert.Throws<InvalidOperationException>(() => {
var calculator = new Calculator(mockDateTime.Object);
});
// Then, Assert
Assert.True(exception.Message.Contains("time to make some maintenance!"));
}
Easy, right? Well, not so fast. We actually made a mistake. Did you catch it? 🧐
The date we set as the maintenance window is wrong. According to the DateTime constructor, it should be 2099, 12 (month), 31 (day) so the year goes first. Let's fix that in the CalculatorProgram and run it again:
public Calculator(IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.GetNow().CompareTo(new DateTime(2100, 1, 1, 0, 0, 0)) >= 0)
{
throw new InvalidOperationException("Hey, it's time to make some maintenance!");
}
}
And that's it! Our tests became green again.
Assert your mock code
You should verify that everything is called from the tested code, typically in the assert section. This ensures the flow of the program and that the code is tested as expected. Additionally, you can check for the number of call to your mocks and ensure they were executed with the Verify
method of the mock:
[Fact]
public void Test_MaintenanceDateHit()
{
// 1) Arrange
var mockDateTime = new Moq.Mock<IDateTimeProvider>();
mockDateTime.Setup(mock => mock.GetNow()).Returns(() => new DateTime(2100, 1, 1, 0, 0, 0));
var exception = Assert.Throws<InvalidOperationException>(() => {
var calculator = new Calculator(mockDateTime.Object);
});
// Then, Assert
Assert.True(exception.Message.Contains("time to make some maintenance!"));
mockDateTime.Verify(mock => mock.GetNow(), Times.Once);
}
By following these steps aligned with the 3A pattern, you can easily integrate mocking straight into your usual unit testing practices.
Let's recap!
Mocking is a technique for mimicking the main dependencies in your code. Components of code that are difficult to test can be identified, isolated, and mimicked in mock objects, which can then interact naturally with your tests in place of the real components themselves. Mocking gives you an added level of assertions for your code and makes your unit tests more robust.