How to Unit Test

Introduction

This tutorial teaches how to unit test the command and mediator components made in IoC+. Unit tests are small tests written in code that validates whether a component is still functioning as it should. This tutorial does not teach unit testing in general. If you’ve never written a unit test in Unity before, we suggest to watch the Unity Test Tools video first. In these examples we make use of the NSubstitute namespace, which is included in the Ioc+ package.

This tutorial starts with a fresh new project. If this is the first time using IoC+, we suggest to read the introduction first.

Testing a Command

Commands are set up to execute a single task, and thus is a perfect subject to test! Let’s write a short command that loads up a player model and dispatches a signal when it’s done loading.

using IoCPlus;

public class LoadPlayerModelCommand : Command {

    [Inject] IPlayerModel playerModel;
    [Inject] IPlayerService playerService;
    [Inject] PlayerModelLoadedSignal playerModelLoadedSignal;

    protected override void ExecuteOverTime() {
        playerService.LoadUsername(OnUsernameLoaded);
    }

    private void OnUsernameLoaded(string username) {
        playerModel.Name = username;
        playerModelLoadedSignal.Dispatch();
        Release();
    }

}

The command injects the player model and player service. It uses the service to load the username and assigns the loaded name to the model. Then it dispatches the signal to notify that the model has been loaded.

Notice that the player model is injected via an interface? This is needed to make the test independent of the concrete player model class. This is what we want, because we want to test only one component at the time!

To properly test this command, we’ll need a substitute class for the IPlayerService as it needs to trigger the OnUsernameLoaded when it is done loading.

using System;

public class PlayerServiceSub : IPlayerService {

    readonly string username;

    public PlayerServiceSub(string username) {
        this.username = username;
    }

    public void LoadUsername(Action<string> onDone) {
        if (onDone != null) {
            onDone(username);
        }
    }

}

A simple class that pretends it is loading a username, which is actually given on its construction. We’ll use this class in the tests for the command, but the actual game would of course never use this class!

Let’s not dwell on the other concrete implementation classes of this example too much, as they can be found in the IoC+ package itself. Let’s write a test to test whether the command sets the model name on execution.

[Test][TestCase("username1337")]
public void LoadsPlayerModel_OnExecute(string username) {
    // Arrange
    LoadPlayerModelCommand command = new LoadPlayerModelCommand();

    IPlayerModel model = Substitute.For<IPlayerModel>();

    DynamicContext context = new DynamicContext();            
    context.Bind<PlayerModelLoadedSignal>();
    context.Bind<IPlayerService>(new PlayerServiceSub(username));
    context.Bind<IPlayerModel>(model);
    context.InjectInto(command);

    // Act
    command.PerformExecution();

    // Assert
    Assert.That(model.Name == username);
}

A lot of new things, let’s cover the code step by step. In the Arrange step, we start by constructing the command that we want to test. Then we use the Substitute class create a substitute for the IPlayerModel interface. We now see why we used an interface to inject the player model, as substitutes are only possible for interfaces.

Then we construct a dynamic context. Dynamic contexts is a component of IoC+ made specially to test commands and mediators. We can add injection bindings to the dynamic context in an object oriented fashion. We bind the PlayerModelLoadedSignal first. Then we bind the IPlayerService class to an instance of the PlayerServiceSub class, which we’ve covered above. We use the username parameter to construct the service, so it will pretend to load the correct username. Lastly, we bind the IPlayerModel to the substitute for the player model. After all injections are bound, we use the InjectInto() method to inject all of the bindings into the command that we’re testing.

In the Act step we call the PerformExecution() method, which is normally called by the IoC+ framework internally. This calls the Execute() and ExecuteOverTime() methods of the command class.

In the Assert step we assert whether the name of the model has been set to the username that was loaded. If this is not the same, we know that our command is not acting as it should!

Let’s write another test to check whether the command is dispatching the PlayerModelLoadedSignal, another responsibility of the command.

[Test][TestCase("username1337")]
public void DispatchesPlayerModelLoadedSignal_OnExecute(string username) {
    // Arrange
    LoadPlayerModelCommand command = new LoadPlayerModelCommand();

    PlayerModelLoadedSignal signal = new PlayerModelLoadedSignal();

    DynamicContext context = new DynamicContext();            
    context.Bind<IPlayerModel>(Substitute.For<IPlayerModel>());            
    context.Bind<IPlayerService>(new PlayerServiceSub(username));
    context.Bind<PlayerModelLoadedSignal>(signal);
    context.InjectInto(command);

    // Act
    command.PerformExecution();

    // Assert
    Assert.That(signal.DispatchCount == 1);
}

The Arrange and Act step is exactly the same! In the Assert step we use the DispatchCount property of the IoC+ signal to assert whether it is dispatch 1 time. If so, the command has effectively dispatched the signal. Using the DynamicContext and the signal’s DisptachCount we can test pretty much anything we would like to do in a command!

Testing a Mediator

Mediators are also perfect to test. The tests of a mediator look a lot like those of commands. It’s important to note that mediators that you want to test will need the interface of its view instead of the concrete implementation class, which we’ll see in a minute. Let’s write a simple mediator class that shows the username when the player model is loaded and dispatches a signal when the name is clicked in its view.

using IoCPlus;

public class PlayerMediator : Mediator<IPlayerView> {

    [Inject] PlayerModelLoadedSignal playerModelLoadedSignal;
    [Inject] PlayerNameClickedSignal playerNameClickedSignal;
    [Inject] IPlayerModel playerModel;

    public override void Initialize() {
        playerModelLoadedSignal.AddListener(OnPlayerModelLoadedSignal);
        view.NameClickedSignal.AddListener(OnViewNameClickedSignal);
    }

    public override void Dispose() {
        playerModelLoadedSignal.RemoveListener(OnPlayerModelLoadedSignal);
        view.NameClickedSignal.RemoveListener(OnViewNameClickedSignal);
    }

    private void OnPlayerModelLoadedSignal() {
        view.ShowName(playerModel.Name);
    }

    private void OnViewNameClickedSignal() {
        playerNameClickedSignal.Dispatch();
    }

}

Note how the mediator inherits from the Mediator<IPlayerView> and thus is bound to the interface type of the view. This way we are free to create a substitute of the view in the tests.

Let’s write a test to validate whether the mediator calls the ShowName() method of its view when the PlayerModelLoadedSignal is dispatched.

[Test][TestCase("username1337")]
public void ShowsPlayerName_OnPlayerModelLoaded(string username) {
    // Arrange
    PlayerMediator mediator = new PlayerMediator();

    IPlayerView view = Substitute.For<IPlayerView>();
    view.DeleteSignal.Returns(new Signal());
    view.NameClickedSignal.Returns(new Signal());
    mediator.SetView(view);

    PlayerModelLoadedSignal signal = new PlayerModelLoadedSignal();

    IPlayerModel model = Substitute.For<IPlayerModel>();
    model.Name.Returns(username);

    DynamicContext context = new DynamicContext();
    context.Bind<PlayerModelLoadedSignal>(signal);
    context.Bind<IPlayerModel>(model);
    context.InjectInto(mediator);

    mediator.Initialize();

    // Act
    signal.Dispatch();

    // Assert
    view.Received(1).ShowName(username);
}

In the Arrange step we construct the mediator that we want to test. We then create a substitute for the IPlayerView. Because mediators in IoC+ use the DeleteSignal internally, we will need to return a signal for it. We’ll also return a signal for the NameClickedSignal, as it is used the mediator that we’re testing. After the substitute view has been fully set up we use the mediator’s SetView() method to assign the view. We construct and cache the PlayerModelLoadedSignal to act and assert later and create a substitute for the player model. After binding the injections and injection them into the mediator using the DynamicContext class we initialize the mediator.

In the Act step we simply dispatch the PlayerModelLoadedSignal. This should trigger the mediator to make its view show the username that has been set in the model.

In the Assert step we use the Substitute’s Received() method to assert whether the ShowName() has been called on the view using the correct username. If this is not called, or called using a different name, we’ll know something is wrong that we’ll need to fix!

Let’s write a test that tests whether the mediator dispatches the PlayerNameClickedSignal when the view tells it so.

[Test]
public void DispatchesPlayerNameClickedSignal_OnViewNameClickedSignal() {
    // Arrange
    PlayerMediator mediator = new PlayerMediator();

    Signal viewSignal = new Signal();

    IPlayerView view = Substitute.For<IPlayerView>();
    view.DeleteSignal.Returns(new Signal());
    view.NameClickedSignal.Returns(viewSignal);
    mediator.SetView(view);

    PlayerNameClickedSignal signal = new PlayerNameClickedSignal();

    DynamicContext context = new DynamicContext();
    context.Bind<PlayerModelLoadedSignal>(new PlayerModelLoadedSignal());
    context.Bind<PlayerNameClickedSignal>(signal);
    context.InjectInto(mediator);

    mediator.Initialize();

    // Act
    viewSignal.Dispatch();

    // Assert
    Assert.That(signal.DispatchCount == 1);
}

Again, the Arrange step looks a lot like the previous test. This time we’re caching the nameClickedSignal that we return in the view’s NameClickedSignal property so we can dispatch it in the Act step. We also cache the PlayerNameClickedSignal that we bind in the dynamic context so we can assert whether it has been called in the Assert step.

In the Act step we simply dispatch the signal of the view. In the Assert step we validate whether the PlayerNameClickedSignal has been dispatched once.

Conclusion

In this tutorial we’ve learned how the command and mediator components of IoC+ can be set up to write relevant unit tests! We’ve seen how it’s important to use interfaces in injections so we can use substitutes in the tests. In most cases we can use the NSubstitute’s Substitute class to dynamically create substitutes but sometimes it is needed to write a custom substitute class. We’ve also learned how to use the DynamicContext class of IoC+ to inject injection bindings into our test subject using a object oriented approach.