Unity scripting can become cumbersome at times without a clear-cut discipline, organization, or framework. Being a component-based design, attaching/detaching or enabling/disabling script “components” on a GameObject which must inherit from a MonoBehavior can get rather clunky with added complexity. Oftentimes we wish to introduce business logic or maintain state without caring about UnityEngine dependencies, and simply hope that it does not alter unrelated functionality, throw off timing, or produce unexpected oddities in other areas of the project.

Enter StrangeIoC. A marvelous, lightweight inversion-of-control framework that offers key features to protect us from inevitable annoyances witnessed in pure Unity architectures. As a classic MVCS, sequestering Unity-related behavior in a View is characterized with this virtue in mind:

“Insulate your app from the chaos often present in views. Mediation allows clean separation of Views from Controllers and Models with no loss of capability.”

Strange has some of the most informative and approachable documentation I’ve read in a while. So I will not attempt to explain here all that Strange is capable of.

If you have background in TDD or writing unit tests in general, you’d quickly find that Unity-dependent code is not unit testable, though it can be behavior or integration tested at a higher level. Most functionality we include in a typical View would not be very valuable to test anyway– e.g. input, graphics, and general look’n’feel. Rather, the lower-level plumbing would be worth having regression tested such that we can run it through automation or continuous integration.

In order to get the desired pieces we want to test “unit-testable”, we will need to Stub or Mock out a few areas of Strange before we write any tests.

Context Stub:

For the sake of bypassing any reliance on UnityEngine in our tests, the ability to add a View needs to be overridden first in the Context. In the Stub below, the highlighted line represents that which differs from the actual Strange functionality. It should be noted that the mediationBinder below is not initialized in the addCoreComponents method. This will have to be set up manually (ideally in a shared [SetUp] function) before the AddView method is ever called.

MediationBinder Stub:

We will need to bind a variation of MediationBinder that instantiates our Mediator and View doubles. The highlighted section once again represents the overridden functionality.

View Mock:

The View itself can be very straight forward and include just a set of pseudo functions that we use for assertion purposes only.

Mediator Stub:

Lastly, the Mediator works similarly to the real thing in that it interfaces directly with our View, except that it does not rely on MonoBehaviour.

Example Unit Test:

At this point we should be able to use our doubles defined above in actual unit tests. For simplicity sake, the following example sets up the Mediator bindings (highlighted) then verifies proper View construction and updates after a Signal is dispatched.

Our Signal, ExampleSignal would be the unit we are testing. This could be a simple Signal that other components listen for locally or be bound to a Command to perform more complex logic sprinkled throughout various parts of the system.

I encourage you to check out Strange and all it has to offer. Personally having worked with Unity for a while, it’s refreshing having something that allows separating out concerns of an app mindset and that which Unity forces us into.

It’s time we all get a little Strange!