Skip to content

AAulicino/Unity-Coroutines-for-NSubstitute

Repository files navigation

Unity Coroutines for NSubstitute

Tests Maintainability openupm

What is it?

Testing and mocking Unity Coroutines can be tricky. This is an extension for NSubstitute that is designed to help you at mocking Unity Coroutines.

Installation

OpenUPM

You can install this package using OpenUPM.

Manual Installation

Unity does not allow specifying a git URL as a dependency of a custom UPM Package.

If you don't have NSubstitute already from another source, add the following to your manifest.json:

"com.aaulicino.nsubstitute": "https://github.com/AAulicino/Unity3D-NSubstitute.git"

After ensuring you have NSubstitue installed, then place this in your manifest.json:

"com.aaulicino.unity-coroutines-for-nsubstitute": "https://github.com/AAulicino/Unity-Coroutines-for-NSubstitute.git"

Basic use

Creating the mock

You can mock the ICoroutineRunner interface by calling:

ICoroutineRunner runner = CoroutineSubstitute.Create();

To preserve the Syntax provided by NSubstitute, this alternate version can be used instead:

ICoroutineRunner runner = Substitute.ForPartsOf<CoroutineRunnerSubstitute>();

Using the mock

Let's use this simple counter class for testing:

public class Counter
{
    public int Current { get; private set; }

    readonly ICoroutineRunner runner;
    Coroutine coroutine;

    public Counter (ICoroutineRunner runner)
    {
        this.runner = runner;
    }

    public void Start ()
    {
        coroutine = runner.StartCoroutine(CounterRoutine());
    }

    public void Stop ()
    {
        runner.StopCoroutine(coroutine);
        coroutine = null;
    }

    IEnumerator CounterRoutine ()
    {
        while (true)
        {
            Current++;
            yield return new WaitForSeconds(1);
        }
    }
}

One thing you might've noticed is that instead of calling StartCoroutine on a MonoBehaviour, we're calling it on the ICoroutineRunner interface. This allows us to mock the runner in our tests.

The Counter can now be tested as follows:

// Arrange
ICoroutineRunner runner = CoroutineSubstitute.Create();
Counter counter = new Counter(Runner);
// Act
Counter.Start();
Runner.MoveNext();
// Assert
Assert.AreEqual(1, Counter.Current);

Calling Runner.MoveNext() will simulate Unity's coroutine update loop.

You can check the CounterTests.cs for test examples on the Counter class.

Since MonoBehaviours implement all methods specified in the ICoroutineRunner interface, you can simply add it to your MonoBehaviour, for example:

public class GameSetup : MonoBehaviour, ICoroutineRunner
{
    Counter counter;

    void Start ()
    {
        counter = new Counter(this);
        counter.Start();
    }
}

Custom Assertions

You can also assert the yielded values from the coroutine:

ICoroutineRunner runner = CoroutineSubstitute.Create();
Counter counter = new Counter(Runner);

Runner.MoveNextAndExpect<WaitForSeconds>();

To assert the amount of seconds configured in the WaitForSeconds object:

ICoroutineRunner runner = CoroutineSubstitute.Create();
Counter counter = new Counter(Runner);

Runner.MoveNextAndExpect(new WaitForSeconds(1));

Other samples can be found in the Samples folder.


You can also have a look at the SystemTests folder as the tests found in there represent some real uses cases.