BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Testing
.NET 3.5+

Mocking Events with Moq

When a class has dependencies that raise events, the reaction to those events should be unit tested. To do so, the dependencies should be isolated from the code under test with the use of test doubles. One option is to use Moq to create mock objects.

Testing Events

In a previous article I described how you can unit test the events raised by a class. In this article we will examine the opposing problem of verifying that a class responds correctly to events raised by its dependencies. As with other unit test scenarios where dependencies are involved, we will substitute with test doubles. We could manually create a fake that includes the events that we wish to test and add a means to raise those events. However, this could be overly complex if the interface that we are faking is large. Instead, we will use mock objects creating with the Moq framework.

Code Under Test

To begin, we need to define the class that we will test and the interfaces that will be mocked. The code under test is a small component from a heating system. The class represents a heating controller that communicates with two dependencies. The first is a thermostat that raises an event when the temperature reaches the upper limit and a second event if it falls below a minimum value. The thermostat includes an asynchronous self-check system that is started by calling a method and returns its results at a later time via a third event.

The second dependency is a heater unit. This includes two methods. One switches the heater on, the other switches it off. The heating controller class will co-ordinate the thermostat and heater to ensure that the temperature remains within an acceptable range.

The interface for the thermostat is shown below:

public interface IThermostat
{
    event EventHandler TooHot;
    event EventHandler TooCold;
    event ThermoEventHandler HealthCheckComplete;
    void StartAsyncSelfCheck();
}

The two events that indicate a temperature outside of the acceptable range are both based upon the EventHandler delegate and, therefore, require no further code. The HealthCheckComplete event uses a custom delegate, which includes a reference to a custom event arguments class, to allow the result of the self test to be provided. The delegate, which should appear in the namespace, is shown below:

public delegate void ThermoEventHandler(object sender, ThermoEventArgs e);

To complete the code for the thermostat we need to create the ThermoEventArgs class. This includes a Boolean value that indicates if the thermostat is functioning correctly.

public class ThermoEventArgs : EventArgs
{
    public bool OK { get; set; }
}

The second dependency to mock is the heater. The interface includes two methods that allow the heating element to be switched on or off:

public interface IHeater
{
    void SwitchOn();
    void SwitchOff();
}

With the dependencies defined, we can create the HeatingController class, shown below. Note that the two dependencies are provided using dependency injection via the class' constructor. The constructor also subscribes to the three events defined in IThermostat. If the temperature is too high, the heater is switched off. If it is too low, the heater is switched on. Additionally, if the thermostat's health check event is raised with event arguments indicating that there is a problem, the heater is switched off for safety purposes. The only public method is PerformHealthChecks, which starts the asynchronous self-test procedure for the thermostat.

public class HeatingController
{
    IHeater _heater;
    IThermostat _thermostat;

    public HeatingController(IHeater heater, IThermostat thermostat)
    {
        _heater = heater;
        _thermostat = thermostat;
        _thermostat.TooHot += new EventHandler(ThermostatTooHot);
        _thermostat.TooCold += new EventHandler(ThermostatTooCold);
        _thermostat.HealthCheckComplete +=
            new ThermoEventHandler(ThermostatHealthCheckComplete);
    }

    public void PerformHealthChecks()
    {
        _thermostat.StartAsyncSelfCheck();
    }

    void ThermostatTooHot(object sender, EventArgs e)
    {
        _heater.SwitchOff();
    }

    void ThermostatTooCold(object sender, EventArgs e)
    {
        _heater.SwitchOn();
    }

    void ThermostatHealthCheckComplete(object sender, ThermoEventArgs e)
    {
        if (!e.OK) _heater.SwitchOff();
    }
}
18 June 2011