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.

Raising Events from Mock Objects

There are various tests that should be performed upon the HeatingController class. In this article we will create four of those tests. The first two tests will ensure that when the high or low temperatures are reached, and the TooHot or TooCold events raised, that the heater is switched of or on respectively. This will be achieved by initialising a HeatingController with a mocked thermostat and heater. When we instruct the mock thermostat to raise one of the events, we will verify the appropriate call against the mock heater.

To raise an event from a mock object we use its Raise method. This accepts two parameters. The first is a lambda expression that includes an empty event subscriber for the event to raise. Although not the most elegant syntax, this is required to allow Moq to understand how the event is used. The second parameter provides the event arguments that will be included with the event. For example, to raise the TooHot event with empty event arguments we will use the following:

mockThermostat.Raise(m => m.TooHot += null, EventArgs.Empty);

The complete test fixture for testing the reaction to the TooHot and TooCold events is as follows:

[TestFixture]
public class ThermostatTemperatureTests
{
    [Test]
    public void TheHeaterIsSwitchedOffWhenTheHighTemperatureIsReached()
    {
        // Arrange
        var mockHeater = new Mock<IHeater>();
        var mockThermostat = new Mock<IThermostat>();
        var controller = new HeatingController(mockHeater.Object, mockThermostat.Object);

        // Act
        mockThermostat.Raise(m => m.TooHot += null, EventArgs.Empty);

        // Assert
        mockHeater.Verify(m => m.SwitchOff());
    }

    [Test]
    public void TheHeaterIsSwitchedOnWhenTheLowTemperatureIsReached()
    {
        // Arrange
        var mockHeater = new Mock<IHeater>();
        var mockThermostat = new Mock<IThermostat>();
        var controller = new HeatingController(mockHeater.Object, mockThermostat.Object);

        // Act
        mockThermostat.Raise(m => m.TooCold += null, EventArgs.Empty);

        // Assert
        mockHeater.Verify(m => m.SwitchOn());
    }
}

Raising Events in Response to Expectations

It is often useful to have mock objects raise events in response to an action. This is particularly true when working with asynchronous calls or when a number of events may be raised in a sequence. To demonstrate this feature of Moq's mock objects, we will raise the HealthCheckComplete event when the StartAsyncSelfCheck method is called. We'll create two tests to cover the scenarios where the health check returns an OK flag of true or false.

To raise an event in response to a call, we must set up an expectation using the Setup method. Instead of using the Returns method to return a value when the expectation is met, we'll use the Raises method to specify that we wish to raise an event. The parameters for Raises are similar to those of Raise. For example, to raise the HealthCheckComplete event with an OK flag set to true when the StartAsyncSelfCheck method is called, we can use the following expectation:

mockThermostat.Setup(m => m.StartAsyncSelfCheck())
    .Raises(m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = true });

We can now create the two tests. Here we check that when the health check reports a problem, the heater is switched off. In the second test the health check reports that all is OK so we verify that the SwitchOff method is never called.

[TestFixture]
public class ThermostatHealthCheckTests
{
    [Test]
    public void TheHeaterIsSwitchedOffWhenTheThermostatReportsAProblem()
    {
        // Arrange
        var mockHeater = new Mock<IHeater>();
        var mockThermostat = new Mock<IThermostat>();
        mockThermostat.Setup(m => m.StartAsyncSelfCheck()).Raises(
            m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = false });
        var controller = new HeatingController(mockHeater.Object, mockThermostat.Object);

        // Act
        controller.PerformHealthChecks();

        // Assert
        mockHeater.Verify(m => m.SwitchOff());
    }

    [Test]
    public void TheHeaterIsNotSwitchedOffWhenTheThermostatReportsOK()
    {
        // Arrange
        var mockHeater = new Mock<IHeater>();
        var mockThermostat = new Mock<IThermostat>();
        mockThermostat.Setup(m => m.StartAsyncSelfCheck()).Raises(
            m => m.HealthCheckComplete += null, new ThermoEventArgs { OK = true });
        var controller = new HeatingController(mockHeater.Object, mockThermostat.Object);

        // Act
        controller.PerformHealthChecks();

        // Assert
        mockHeater.Verify(m => m.SwitchOff(), Times.Never());
    }
}
18 June 2011