It’s always easier to create unit tests when we can use mocks. With it, it’s easier to set up our scenario, verify our assertions, and so on. But sometimes, instances are created and disposed within a method, leaving us no opportunity to analyze what was created. So then, how can we verify that we have manipulated that instance accordingly?
Scenario
So, let’s suppose that we are trying to create unit tests for the Oven class below. 20 minutes after starting, the oven shall make a loud *DING* sound, so the user knows that whatever was cooking is now ready to be eaten. We don’t want to be the guilty part of transforming a nice and delicious pizza into a chunk of charcoal right? Let’s analyze our code below:
public class Oven
{
private int temperature;
private bool hasStarted;
public void Start()
{
hasStarted = true;
temperature = 200;
var dingTimer = new Timer(TimeSpan.FromMinutes(20).TotalMilliseconds);
dingTimer.Elapsed += Timer_Elapsed;
dingTimer.Start();
}
private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
{
MakeLoudNoise();
}
private void MakeLoudNoise() { }
}
There are 2 conditions that we have to assert here to reach our objective. First is that we are starting the timer, and the other is that we are sounding the bell after the timer has finished. In this tutorial, we will only be covering how to do the former, since it will be enough to accomplish the objective of this post.
Verifying that we are running the timer
Since the timer is created inside the method, we cannot achieve that with the current implementation, so we will have to make some changes. It’s worth mentioning that you should be careful when modifying your code in order to test it. For example, we shouldn’t be setting property accessors to public randomly, just because we need to verify their value. But in some cases, like this one, the code is not testable, so the only option is to fix it.
In order for us to test this code, we need to create a TimerFactory and receive it via the constructor in the Oven class, in order for us to be able to mock it. In addition, our factory cannot simply return a Timer, otherwise we won’t be able to properly mock it, meaning that we also need an ITimer interface. It might sound confusing, so let’s see some code.
public interface ITimer
{
event ElapsedEventHandler Elapsed;
void Start();
}
public class DefaultTimer : ITimer
{
private Timer internalTimer;
public event ElapsedEventHandler Elapsed
{
add { internalTimer.Elapsed += value; }
remove { internalTimer.Elapsed -= value; }
}
public DefaultTimer(int minutesBetweenEvents)
{
internalTimer = new Timer(TimeSpan.FromMinutes(minutesBetweenEvents).TotalMilliseconds);
}
public void Start()
=> internalTimer.Start();
}
Firstly, we start by creating the ITimer interface and our implementation. Our implementation, the DefaultTimer, is merely a wrapper for the Timer class, as we don’t want to change the behavior at all.
public interface ITimerFactory
{
ITimer Create();
}
public class TimerFactory : ITimerFactory
{
public ITimer Create()
{
return new DefaultTimer(20);
}
}
Then we have our ITimerFactory class and implementation, which is pretty straightforward. We could receive the timer as a parameter, and even the action that should be performed when it has elapsed, but let’s keep it simple.
public class Oven
{
private int temperature;
private bool hasStarted;
private readonly ITimerFactory _timerFactory;
public Oven(ITimerFactory timerFactory)
{
_timerFactory = timerFactory;
}
public void Start()
{
hasStarted = true;
temperature = 200;
var dingTimer = _timerFactory.Create();
dingTimer.Elapsed += Timer_Elapsed;
dingTimer.Start();
}
private void Timer_Elapsed(object? sender, ElapsedEventArgs e)
{
MakeLoudNoise();
}
private void MakeLoudNoise() { }
}
And then, on the Oven class, we’ve added a new constructor that receives an ITimerFactory, which allows us to mock it later. Now let’s see how our unit tests turn out.
[Test]
public void Start_ShouldStartTimer()
{
// Arrange
var timerFactoryMock = new Mock<ITimerFactory>();
var timerMock = new Mock<ITimer>();
timerFactoryMock.Setup(x => x.Create()).Returns(timerMock.Object);
var oven = new Oven(timerFactoryMock.Object);
// Act
oven.Start();
// Assert
timerMock.Verify(x => x.Start());
}
With the proper structure in place, creating the test becomes super simple as you can see. All that we have to do is to setup the return of the TimerFactory’s Create method to return a mock of ITimer, and then verify that the Start method has been called.
Also, if you would like more explanations regarding the name of the unit test, you can read it here.
Conclusion
Whenever we are facing a situation in which an instance is being created that we have no access to, one way to solve the issue is by introducing a factory. With this approach, we can maintain the current behavior and make our code testable.