null theorem

I code things.

Testing Observable Models

2019-04-21

Observable models are a valuable pattern for creating models which can evolve in a managable way, however isolating state from contracts is only one aspect of evolving models in a manner that reduces the chance of unintended consequences resulting from change. Automated testing supports an evolving model by providing a means to verify that behaviour doesn’t change unexpectedly as new behaviours are added to the model or the internal structure of the model is refactored.

The observable model pattern keeps the testing surface simple: commands go in and events come out of the model, which maps quite cleanly to actions and assertions in automated tests. When a model’s commands are simply void methods then all that’s required to execute a command action from a test is to call the method on the model with the appropriate parameters. To assert that an event was emitted from an observable model, however, requires a fake observer which can listen for events emitted by the model. Rather than hand coding such a fake I prefer to use a mocking library with built in support for call recording and call pattern assertions.

Currently I find NUnit to be a convenient unit test framework to use with Unity, given that it’s included by default with Unity, however the NSubstitute mocking library takes a bit more work to add to your Unity project. Nonetheless, a mocking library like NSubstitute is quite handy and worth the one-time annoyance of importing the DLL manually.

For an example of testing observable models, consider the model presented in my prevous post on Observable Models. The two important abstractions of this model are the ICounter interface and the CounterEvent abstract class, with the Counter class being an implementation of the ICounter interface.

public abstract class CounterEvent { }
public interface ICounter 
{
    IObservable<CounterEvent> Events { get; }

    void Increment();
}
public class Counter : ICounter
{
    ...
}

Testing the Counter implementation simply involves calling the Increment() command method and observing CounterEvents emited by the model on the Events observable. The first test we’ll consider asserts that an IncrementedEvent is emitted with the correct value when the Increment() method is called on the model the first time. The Received() method provided by NSubstitute can be used to perform this assertion on the fake observer.

using System;
using NSubstitute;
using NUnit.Framework;

public class CounterTests
{
    // System-under-test instance of the model
    private ICounter _counter;
    
    // Fake observer to listen to events
    private IObserver<CounterEvent> _fakeObserver;

    [SetUp]
    public void Setup()
    {
        // Instantiate model
        _counter = new Counter();

        // Create fake observer mock using NSubstitute
        _fakeObserver = Substitute.For<IObserver<CounterEvent>>();

        // Subscribe the fake observer to the model
        _counter.Events.Subscribe(_fakeObserver);
    }

    [Test]
    public void IncrementOnce() 
    {
        // Act
        _counter.Increment();

        // Assert
        _fakeObserver.Received().OnNext(new IncrementedEvent(1));
    }
}

The IncrementOnce test shows how to assert that a single event was emitted by the model, however it’s often necessary to assert that a sequence of events was emitted in order by the model. This can be handled by the Received.InOrder() static method provided by NSubstitute.

public class CounterTests
{
    ...

    [Test]
    public void IncrementTwice() 
    {
        // Act
        _counter.Increment();
        _counter.Increment();

        // Assert
        Received.InOrder(() => 
            {
                _fakeObserver.Received().OnNext(new IncrementedEvent(1));
                _fakeObserver.Received().OnNext(new IncrementedEvent(2));
            }
        );
    }
}

In addition to asserting that events were emitted by the model, it can often be useful to assert that particular events were not emitted. The DidNotReceive() method provided by NSubstitute for the fake observer can perform this kind of assertion. For instance, the IncrementOnce test can be augmented to assert that an IncrementedEvent with a value of 2 was not emitted by the model.

public class CounterTests
{
    ...
    
    [Test]
    public void IncrementOnce()
    {
        // Act
        _counter.Increment();

        // Assert
        _fakeObserver.Received().OnNext(new IncrementedEvent(1));
        _fakeObserver.DidNotReceive().OnNext(new IncrementedEvent(2));
    }
}

For readability, as well as a clean separation of concerns, I often prefer to isolate the details of testing away from the tests themselves. By defining a “Fixture” base class for testing a given model, common details about how the tests are performed can be encapsulated away from the test code, which in turn permits easier changes to these details as the model evolves. Furthermore, it becomes easier to split the tests for a given model among a number of files, which can often help with readability and discoverability as a model grows more complex.

using System;
using NSubstitute;
using NUnit.Framework;

public class CounterTestFixture
{
    private ICounter _counter;
    private IObserver<CounterEvent> _fakeObserver;

    [SetUp]
    public void Setup()
    {
        _counter = new Counter();
        _fakeObserver = Substitute.For<IObserver<CounterEvent>>();

        _counter.Events.Subscribe(_fakeObserver);
    }

    protected void Act_Increment()
        => _counter.Increment();

    protected void Assert_EventObserved(CounterEvent expected)
        => _fakeObserver.Received().OnNext(expected);

    protected void Assert_EventsObserved(params CounterEvent[] expected)
        => Received.InOrder(() => 
        {
            foreach (var expectedEvent in expected)
                _fakeObserver.Received().OnNext(expectedEvent);
        });

    protected void Assert_EventNotObserved(CounterEvent prohibited)
        => _fakeObserver.DidNotReceive().OnNext(prohibited);
}
using NUnit.Framework;

public class CounterTests : CounterTestFixture
{
    [Test]
    public void IncrementOnce()
    {
        Act_Increment();

        Assert_EventObserved(new IncrementedEvent(1));
        Assert_EventNotObserved(new IncrementedEvent(2));
    }

    [Test]
    public void IncrementTwice()
    {
        Act_Increment();
        Act_Increment();

        Assert_EventsObserved(
            new IncrementedEvent(1),
            new IncrementedEvent(2)
        );
    }
}

Note that the instance of the model _counter, as well as the mocked event observer _fakeObserver, are both private to the CounterTestFixture class. This is intentional, and respecting this privacy is key to keeping the details of testing out of the test methods themselves. Instead, test methods should only ever make use of the protected Act_ and Assert_ (and possibly Arrange_) methods provided by the fixture class. Furthermore, I know it likely seems a bit funny to see an _ in the middle of a C# method name, but I find that the value of the intent behind a method call in a test method being immediately apparent (without resorting to comments such as // Arrange, // Act, and // Assert) outweighs the unorthodoxy of this syntactic oddity.

Automated testing supports the evolution of a model’s implementation, and clean testing patterns support the evolution of a model’s test suite. Both of these aspects contribute to managing the complexity of an evolving model; well maintained implementation code verified by complicated test code is often just as difficult and risky to change as code without tests. When applied to the observable model pattern, the test fixture pattern presented above can help you create easy to change models with a descriptive test suite that acts as living documentation for the model.

Once you have a clean and evolvable model and test suite, the next step is to drive the evolution of the model from the test suite by writing the tests first, and then only writing enough model code to pass those tests, so that you can be confident that all of the important behaviour of your model is verified by a test. But that’s a whole other subject

Observable Models

2019-04-13
Recently I’ve been trying out a form of domain modeling in Unity which is inspired by the Event Sourcing pattern, and in particular the principle of the events being the source of truth for the model. By disallowing direct querying of the model’s state, and instead only permitting read dependencies on a stream of events, the model’s internal data structure can be isolated and evolved independently from its contracts. The separation of model and presentation concerns greatly helps with preventing your game’s code from turning into a difficult to change ball of mud, and further separating the model’s internal data structure from its contract allows the internal details of the model to evolve without affecting the presentation code, which makes change easier and less prone to introducing bugs. Continue reading