The tricky cases in testing - Events

This article is a revised version. It was previously published at refactoring-legacy-code.net published.

This is a series of articles. You can find the other articles here:

Automated event testing with NUnit

Events play a major role in .NET applications. Large parts of the connection between the UI and "the rest" of the application are often based on events. There are also many places outside the UI where events are very useful. The topic of asynchronous calls should be mentioned here in particular. Asynchronous calls often consist of a method that is used to start the asynchronous process and an event that is used to deliver the result as soon as it is available.

At events, there are four topics that are relevant for automated testing:

  • Check whether an event is triggered. It may be necessary to check whether the data supplied meets expectations.
  • Check whether an event handler is required.
  • Check whether a binding to the event takes place, i.e. whether it leads to a Subscribe or vice versa, whether the event binding is removed again (Unsubscribe).
  • Triggering the event in the test to check whether other functional units then react as expected.

Check whether an event is triggered

The following class serves as an example for an automated test that checks whether an event is triggered.

public class Sut
{
    public event Action AnEvent = delegate { };

    public event Action OnEventWithData;

    public void DoSomething() {
        AnEvent();
    }

    public void DoSomeOtherThing(string daten) {
        AnEventWithData(data);
    }
}

The class has two methods, each of which triggers an event. The event AnEvent will be provided by the DoSomething method and is parameterless. This event is linked to delegate { } an empty event handler is attached so that the event can always be triggered. Even if the user of the class does not register a handler with +=, no zero exception is thrown because the empty default handler is always attached. For event handlers that are optional, this is syntactically the most elegant form up to C# 6.0.

In C# 6.0, the problem can be solved with a question mark, as the following listing shows.

AnEvent?.Invoke();

The question mark first checks whether AnEvent possibly zero is. Only if this is not the case will the Invoke method is called and the event is triggered.

An automated test that checks whether the event is triggered looks like this:

[TestFixture]
public class EventTests
{
    private Sut sut;

    [SetUp]
    public void Setup() {
        sut = new Sut();
    }

    [Test]
    public void Event_is_triggered() {
        var count = 0;
        sut.OnEvent += () => count++;

        sut.DoSomething();

        Assert.That(count, Is.EqualTo(1));
    }
}

In the test, a lambda expression is appended to the event. Within this, the variable count is incremented each time the event is triggered. In this way, after calling the DoSomething method to check whether the event has been triggered exactly once.

Check the dates of the event

In the case of an event that supplies data, this can be copied into a variable of the test method in the lambda expression. This allows them to be checked after the event has been triggered.

[Test]
public void Check_data_of_event() {
    var eventData = "";
    sut.AnEventWithData += x => eventData = x;

    sut.DoSomeOtherThing("x");

    Assert.That(eventData, Is.EqualTo("x"));
}

In this example, the event returns the string used as the parameter of the DoSomeOtherThing method was specified, in this case an "x". In the event handler, I copy the parameter of the event into a variable, which can then be checked after the method is called.

Check whether a handler is required

For events that are optional, an automated test should check that immediately after instantiating an object of the class, no zero exception is thrown when the event is triggered. The aim here is to find out whether the event was triggered on zero is checked.

[Test]
public void Eventhandler_is_optional() {
    Assert.DoesNotThrow(() => new Sut().DoSomething());
}

The test checks whether DoSomething no exception is triggered from the example above.

Check for Subscribe and Unsubscribe

Sometimes it is necessary to check whether a functional unit connects to an event (subscribe) or disconnects from it (unsubscribe). Binding to an event usually takes place with the += operator. Conversely, the binding can be released again with -=.

The following example shows how the check can be carried out using TypeMock isolator [https://typemock.com] can take place.

using System;
using NUnit.Framework;
using TypeMock.ArrangeActAssert;

namespace events
{
    [TestFixture, Isolated]
    public class EventSubscriberTests_TypeMock
    {

        [Test]
        public void Subscribes_to_Event() {
            var withEvent = Isolate.Fake.Instance();
            var sut = new Sut(withEvent);

            sut.DoSomething();

            Isolate.Verify.WasCalledWithAnyArguments(
                () => withEvent.MyEvent += null);
        }

        public class WithEvent
        {
            public void DoSomething() {
                MyEvent();
            }

            public event Action MyEvent;
        }

        public class Sut
        {
            private readonly WithEvent withEvent;

            public Sut(WithEvent withEvent) {
                this.withEvent = withEvent;
            }

            public void DoSomething() {
                withEvent.MyEvent += delegate { };
            }
        }
    }
}

The class Sut is from the class WithEvent dependent. The constructor expects Sut an instance of WithEvent. The DoSomething method binds an event handler with += to the event. And it is precisely this call of += that is to be checked in the test. To do this, I inject a mockup of WithEvent. This is done with the help of TypeMock isolator is generated. This allows the test to check whether certain methods have been called on the mock-up. In the example above Isolate.Verify.WasCalledWithAnyArguments checks whether the method specified in the lambda expression, in this case the += operator, has been called. The parameter is ignored.

Without the use of TypeMock isolator For this test scenario, a different way is required to test on the injected WithEvents object to check whether the += operator has been called. It is not necessary to use a mock framework that is based on the Profiler API. Various open source mock frameworks can also check whether the += operator has been called.

Triggering an event

When a functional unit reacts to an event, an automated test should ensure that the desired behavior occurs. To do this, it is often necessary to trigger an event in the test.

The following two classes serve as an example.

public class Sut
{
    private readonly WithEvent withEvent;
    private int count;

    public Sut(WithEvent withEvent) {
        this.withEvent = withEvent;
        this.withEvent.MyEvent += () => count++;
    }

    public int Count => count;
}

public class WithEvent
{
    public event Action MyEvent;
}

The class WithEvent has an event to which the class can connect. Sut binds. When the event is triggered, the Sut up one counter. The class WithEvent has no way of triggering the event from outside. This is also the case in real classes. The definition of an event with the keyword "event" means that only the operations "+=" and "-=" are available outside the class in which the event is defined. Calling the Invoke method is not syntactically possible:

withEvent.MyEvent.Invoke(); // Syntactically not possible

TypeMock Isolator is once again to the rescue. The following test shows how the event can be triggered.

[Test]
public void Responds_to_the_event() {
    var withEvent = Isolate.Fake.Instance();
    var sut = new Sut(withEvent);
    Assert.That(sut.Count, Is.Zero);

    // withEvent.MyEvent.Invoke(); // syntactically not possible
    Isolate.Invoke.Event(() => withEvent.MyEvent += null);

    Assert.That(sut.Count, Is.EqualTo(1));
}

First of all, I create the following by calling Isolate.Fake.Instance() a dummy of the type WithEvent. This dummy is added as a dependency to the Sut is submitted. This means that the event can then be started with Isolate.Invoke.Event are triggered. The += operator is used with TypeMock Isolator to define which event is meant using Lambda Expression. So here it is not zero to the event, but in this way the isolator is informed which event is to be triggered.

Conclusion

Handling events in automated tests is usually not a major problem. In most cases, it is sufficient to attach a lambda expression as an event handler and then check whether it is called. The more complicated cases are again easier to put under test if the right tool is available. Mock frameworks can help here. In this case, it doesn't even have to be the "magic bullet", but one of the open source frameworks is often sufficient.

Our seminars

course
Clean Code Developer Basics

Principles and tests - The seminar is aimed at software developers who are just starting to deal with the topic of software quality. The most important principles and practices of the Clean Code Developer Initiative are taught.

to the seminar "
course
Clean Code Developer Trainer

Conducting seminars as a trainer - This seminar is aimed at software developers who would like to pass on their knowledge of Clean Code Developer principles and practices or Flow Design to others as a trainer.

to the seminar "

Leave a Comment

Your email address will not be published. Required fields are marked *

en_USEnglish