Mocking instances created within the tested class in .NET
Mocks drastically reduce the amount of setup code needed for creating unit tests. However, sometimes, instances are created and disposed of within a method, leaving no opportunity to analyze what was created. For this answer, a thorough understanding of the
Let's understand how mocking instances are created within the tested class using the following code example.
Problematic code
In the Start method below, we create a new timer which we don't have access to. This makes it impossible for us to verify what happened to it. We can say that this code is not
public class Oven{private int temperature;private bool hasStarted;public void Start(){// Sets the variable value that indicates time is started or nothasStarted = true;// Sets the temperature variable valuetemperature = 200;// Creates a timer that shall elapse in 20 minutesvar dingTimer = new Timer(TimeSpan.FromMinutes(20).TotalMilliseconds);// Subscribes to the elapsed eventdingTimer.Elapsed += Timer_Elapsed;// Starts the timerdingTimer.Start();}private void Timer_Elapsed(object? sender, ElapsedEventArgs e){// Do something}}
Solution
There are three necessary steps to make this code testable:
Create an
ITimerFactoryandITimerinterfaces, to mock and verify what happens withTimerFactoryandTimerinstances, respectively.Create a
TimerFactoryandTimerto provideITimerFactoryandITimerimplementations, respectively.Use the
TimerFactoryin theOvenclass to create timers.
In the solution below, we can see the steps implemented. It is now possible to create unit tests for the oven class that can benefit from mocking frameworks.
public interface ITimerFactory{ITimer Create();}
Code explanation
In
ITimer.csfile: This file only contains the definition of theITimerinterface. It does not have any executable code.In
Timer.csfile: This file contains the implementation of theDefaultTimerclass, which implements theITimerinterface. TheDefaultTimerclass initializes an internal timer instance in its constructor, setting the interval based on the minutes between events. TheElapsedevent of the internal timer is mapped to the corresponding event of theDefaultTimerclass. This allows event handlers to be attached and detached from the Elapsed event of theDefaultTimerclass. TheStart()method starts the internal timer.In
ITimerFactory.csfile: This file defines theITimerFactoryinterface, which declares aCreate()method for creatingITimerinstances.In
Oven.csfile: This file contains theOvenclass, which represents an oven. TheOvenclass has a constructor that takes anITimerFactoryinstance as a dependency. TheStart()method is called to start the oven. TheCreate()method of theITimerFactoryis called to create a timer object. An event handler (Timer_Elapsed) is subscribed to theElapsedevent of the timer. The timer is started by calling itsStart()method. A message is written to the console to indicate that the oven has been started.In
Tests.csfile: This file contains theTestClassclass with theStart_ShouldStartTimer()test method. The test method is annotated with the[Test]attribute, indicating that it’s a unit test. Inside the test method: a mock object of theITimerFactoryinterface (timerFactoryMock) is created using theMockclass from theMoqframework. Another mock object of theITimerinterface (timerMock) is created. TheCreate()method of theITimerFactorymock is set up to return theITimermock. An instance of the oven class is created, passing theITimerFactorymock as a parameter. TheStart()method of theOveninstance is called. TheStart()method of theITimermock is verified to ensure it has been called.
Free Resources
- undefined by undefined