C# – Unit test an event handler

An event handler is a method that is registered to listen to an event. When the event is invoked, the event handler method is called. You may be tempted to directly call the event handler to unit test it. It’s better to actually raise the event though, and then check the side effects of the event handler. That proves it’s all wired up and handling the event properly.

Here comes the tricky part: an event can only be invoked from the class containing it. That’s usually done by a public method in the class that’s also doing other stuff (which you probably don’t want running in your test). You can solve that problem by mocking out the event and invoking it in the test. This is similar to how you solve the problem of unit testing when static methods are involved.

Class with event handler to test

First, here’s the class containing the event handler (highlighted) that we want to test:

public class MessageProcessor
{
    private readonly IMessageRepository Repository;
    public MessageProcessor(IMessageRepository repo, IMessageReceiver receiver)
    {
        Repository = repo;
        receiver.NewMessage += AddMessage;
    }
    public void AddMessage(object? sender, string message)
    {
        Repository.Save(message);
    }
}
Code language: C# (cs)

The MessageProcessor class is dependent on the event. The event is contained in the IMessageReceiver interface, which is being dependency injected via the constructor. This makes it possible to isolate and mock out the event.

Raise the event with Moq in a unit test

We want to mock out the interface containing the event, pass the mock into the class under test (shown above), and then raise the event. In this example, the event handler saves the message to a repository (which we’re also mocking out and dependency injecting), so we can check if the Save() method was called.

The best way to create mocks is by using a mocking library, such as Moq. If you don’t have it already, install the Moq package (this is using Package Manager Console in Visual Studio):

Install-Package Moq
Code language: PowerShell (powershell)

Now write the unit test. Create the mocks and dependency inject them. Then invoke the event with Mock.Raise() using the syntax shown (highlighted):

using Moq;

[TestMethod()]
public void TestEventHandler()
{
	//arrange
	var mockReceiver = new Mock<IMessageReceiver>();
	var mockRepo = new Mock<IMessageRepository>();
	var processor = new MessageProcessor(mockRepo.Object, mockReceiver.Object);
	string message = "abc";

	//act - Invoke the event (object? sender, string message)
	mockReceiver.Raise(t => t.NewMessage += null, this, message);

	//assert - Verify the side effects of the event handler
	mockRepo.Verify(t => t.Save(message), Times.Once());
}
Code language: C# (cs)

Mock.Raise() invokes the event, which calls the event handler method, which then calls the Save() method on the repository. The test verifies this behavior and passes.

TargetParameterCountException: Parameter count mismatch

If you don’t pass in the right number of arguments to Mock.Raise(), you’ll get an exception:

Test method TestEventHandler threw exception:
System.Reflection.TargetParameterCountException: Parameter count mismatch.

This method has some strange-looking syntax, but let’s break it down. Here’s the method signature:

Raise(Action<T> eventExpression, params object[] args)
Code language: C# (cs)
  • eventExpression: Pass in a lambda that adds a null event handler to the event. We want to invoke the NewMessage event, hence: t => t.NewMessage += null
  • args: Pass in all of the event handler’s parameters. In this case, it has two parameters: object? sender and string message. Note: We pass in ‘this’ for the sender parameter. It’s not being used in the event handler, so it makes no difference what is passed in.