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.

What does that mean in practice? That the only readable property of the model is an event stream. In addition to the event stream, the model contains a number of command methods, which to keep things simple I’ve just been sticking to void methods receiving value (or immutable value object) parameters. It’s basically Command-query separation where the query side of the model is an event stream.

For example, consider the contract of a simple model of an incrementing counter: the model accepts an Increment() command, and emits an IncrementedEvent containing the new value of the counter. This can be represented in C# as an interface and some classes to represent the events.

using System;

// The model's contract
public interface ICounter 
{
    // Just one public property: the event stream
    IObservable<CounterEvent> Events { get; }

    // One or more command methods
    void Increment();
}

// Base class for Counter events
public abstract class CounterEvent { }

// Immutable event class
public class IncrementedEvent : CounterEvent
{
    public int NewCount { get; }

    public IncrementedEvent(int newCount)
    {
        NewCount = newCount;
    }
}

Presumably, an implementation of the ICounter interface would need to include an int state variable to keep track of the current value of the counter, but that detail is kept hidden away from any other object which depends on the counter model. This might not seem very important when the structure of the state is simply an integer, but when dealing with data structures of greater complexity it can be extremely valuable to be able to change the internal structure of a model as it evolves to meet new requirements. For instance, changing the indexing and the way data is split up amongs various data structures can improve performance or make algorithms simpler to implement, however when these structures are exposed publicly then changes to them will require all dependencies to change as well. When the structure of the internal data is kept private and only events are exposed publicly, then that structure is free to change without effecting external dependencies, so long as events are still emitted when they are expected.

I’ve been using UniRx for my reactive needs in Unity, which makes implementing the IObservable<CounterEvent> part of the contract quite simple (I also appreciate that it’s on the Asset Store, which makes it really convenient to import into projects). All that’s required is a private Subject<CounterEvent> which fulfills both IObservable<CounterEvent> and IObserver<CounterEvent>; when OnNext(...) is called on the subject with an event, it will be sent to all of the observers subscribed to the event stream.

using System;
using UniRx;

public class Counter : ICounter
{
    // Internal subject for the event stream
    private Subject<CounterEvent> _events = new Subject<CounterEvent>();

    // The model's internal state
    private int _count;

    // Just exposing the IObservable side of the subject publicly
    public IObservable<CounterEvent> Events => _events;

    // Implementation of the command
    public void Increment()
    {
        // Mutate state
        _count++;

        // Emit event
        _events.OnNext(new IncrementedEvent(_count));
    }
}

This kind of model can then be integrated with Unity by referencing it from MonoBehaviour scripts attached to GameObjects in the scene hierarchy, which then either react to user input or other events in the scene and call commands on the model, or react to events in the model and cause changes in behaviour on components on the GameObject. I personally prefer to use the Zenject IoC container (also on the Asset Store) to connect the models to the MonoBehaviours in my Unity scenes.

using UnityEngine;
using UnityEngine.UI;
using UniRx;
using Zenject;

// Presenter for a Text field containing the current count
public class CountTextPresenter : MonoBehaviour
{
    // Reference to the Text component
    public Text CountText;

    [Inject] // Method injection from Zenject
    public void Initialize(ICounter counterModel)
    {
        // Filter out IncrementedEvents and update text on those events
        counterModel.Events
            .OfType<CounterEvent, IncrementedEvent>()
            .Subscribe(e => CountText.text = e.NewCount.ToString());
    }
}
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using Zenject;

// Presenter for a Button to increment the count
public class IncrementButtonPresenter : MonoBehaviour
{
    // Reference to the Button component
    public Button IncrementButton;

    [Inject] // Method injection from Zenject
    public void Initialize(ICounter counterModel)
    {
        // Call Increment() when Button is clicked
        IncrementButton.onClick.AddListener(() => counterModel.Increment());
    }
}
using Zenject;

// Zenject Installer added to Scene Context
public class SceneInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        // Bind the Counter model as a singleton
        Container.Bind<ICounter>().To<Counter>().AsSingle();
    }
}

If these MonoBehaviours can be kept simple and just act as the glue between the model and Unity components rather than containing complicated state and logic, then complexity can be evolved in the model where it’s easy to test and change it in isolation from the complexity of Unity. By cleanly separating the abstract game logic of the model from the details of presenting the game in Unity through well defined event-based contracts that don’t leak the model’s data structure, these concerns can be changed independently without requiring a change to presentation concerns. Making the most common changes easy is one the fundamental goals of architecture, and observable models are a further refinement on the architectural decision to separate domain logic from presentation.

As with event sourcing in general, this pattern is not without its challenges. Not being able to directly query the state of the model requires a change in thinking about dependencies, and deciding on the separation between presentation behaviour and doman logic in the context of a game is not always straightforward. However if you’ve been running up against the problems of tight coupling between your presentation components or a difficult to change domain model, then you might find some value in the observable model pattern.


If you’re interested in learning more about using the Zenject container to inject models into presentation components, check out this tutorial I made on Dependency Injection in Unity with Zenject. It involves code that’s quite similar to the stuff shown here, except that it uses basic C# events instead of the reactive abstractions IObservable<T> and IObserver<T>.