A Uniject example

Let’s take a look at one of the example scenes that comes with Uniject – the bleeping, bouncing spheres!

We’re going to see how we can:

  • Write a TestableComponent that drives our bouncing lights
  • Have Uniject create the bouncing light GameObject hierarchy for us
  • Load and verify an audio clip and physic material
  • Test all of our code
  • Do all of the above without opening the Unity editor!

Here’s a functional spec for the scene:

  • It has a thin box on the floor
  • It features random, bouncing, light emitting spheres
  • When the spheres collide, they beep and change their light colour

The lights are derived from Uniject’s ‘TestableComponent’ class, which is semantically equivalent to MonoBehaviour:

[GameObjectBoundary]
public class BouncingLight : TestableComponent {

    private ILight light;
    private Random rand;
    private IAudioSource source;
    private UnityEngine.AudioClip beep;

    public const float killThresholdY = -5.0f;

    public BouncingLight(TestableGameObject obj, Sphere sphere,
                         IAudioSource source,
                         ILight light, Random rand,

                         [Resource("physic/bouncy")]
                         IPhysicMaterial material,

                         [Resource("audio/beep")]
                         AudioClip beep) : base(obj) {
        this.light = light;
        this.rand = rand;
        this.beep = beep;
        this.source = source;
        sphere.collider.material = material;
        light.intensity = 2.0f;
        light.range = 5;
    }

    public override void Update() {
        if (this.Obj.transform.Position.y < killThresholdY) {
            Obj.Destroy();
        }
    }

    public override void OnCollisionEnter(Collision collision) {
        light.color = new Color((float) rand.NextDouble(),
                                (float) rand.NextDouble(),
                                (float) rand.NextDouble());
        source.playOneShot(beep);
    }
}

This code is all that is required for Uniject to instantiate a complete, working GameObject hierarchy including referenced audio clip and physic material.

Now for the biggest advantage; we can execute every line of this code in a fraction of a second, without opening Unity at all!

Here is a test that instantiates our bouncing light, simulates a frame and ensures it has not destroyed itself:

    [TestFixture]
    public class TestCollisions : BaseInjectedTest {

        [Test]
        public void testBouncingLightDoesNotDestroyAboveThreshold() {
            BouncingLight light = kernel.Get<BouncingLight>();
            step();
            Assert.IsFalse(light.Obj.destroyed);
        }
}

And here’s the test runner in MonoDevelop 3.0:

So we’ve tested our code, how do we instantiate a TestableComponent in Unity?

Enter Ninject:

BouncingLight light = UnityInjector.get().Get<BouncingLight>();

And our Bouncing Light is created for us:

It’s worth pointing out that this GameObject hierarchy has been created for us automatically by Uniject; it has a light, sphere collider, rigid body and audio source, and  it has the audio clip of our beep sound and the bouncy physic material.

There is no prefab required and no object construction code.

There is one further class involved that sets up the scene and randomly spawns the bouncing lights. It, too, is testable:

public class TestableCollisions {
    public TestableCollisions(IntervalBasedCallback caller,
                              [GameObjectBoundary] Box box,
                              Factory factory,
                              Random rand) {

        caller.callback = () => {
            BouncingLight light = factory.create();
            light.Obj.transform.Translate(
                new Vector3((float) rand.NextDouble(),
                4 + (float) rand.NextDouble() * 10.0f,
                (float) rand.NextDouble()));
        };

        caller.interval = TimeSpan.FromSeconds(2);
        box.Obj.transform.localScale = new Vector3(50, 1, 50);
    }
}

Here is an integration test that instantiates the entire scene and simulates a few seconds worth of frames, to verify that it is spawning bouncing spheres:

[Test]
public void testExampleSpawnsObjects() {
    kernel.Get();
    step(2);
    int count = objectCount;
    step(4);
    Assert.Greater(objectCount, count);
}

But you’re referencing resources with strings!

It’s true, we’ve totally subverted Unity’s convention of editor assigned references; if we change the location of our resources won’t we break our code?

The difference is we’ll know about it immediately if we break a referenced resource; Uniject verifies that referenced resources actually exist. In a fraction of a second a test can verify every referenced resource in our entire project, and we don’t even need to fire up Unity to do so.

I wouldn’t care to know how much time I’ve wasted due to the brittleness of editor assigned references; Uniject has the potential to eliminate a swathe of runtime errors.

Note that we have a separate test solution in a sibling folder to the Assets folder. This solution contains the test project where the unit tests are actually written, and it references the .csproj files that are automatically created and managed for us by Unity.

We also use the latest version of MonoDevelop, 3.0, rather than the borked version that ships with Unity – it has a broken unit test runner.

Browse Uniject on Github.

Advertisements