Introduction To Unity Unit Testing

Learn all about the Unity Test Framework and how to set up Unit Tests in your Unity projects. By Ben MacKinnon.

5 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Passing Tests

Great job! You’ve written your first unit test, but how do you know if it works? The Test Runner of course! In the Test Runner window, expand all the arrows. You’ll see your AsteroidsMoveDown test in the list with a gray circle:

ASteroidsMoveDown

The gray circle means the test hasn’t yet been run. When a test is run and passes, it’ll show a green arrow. If a test fails, it’ll show a red X. Run the test by clicking the RunAll button.

Run All button

This will create a temporary scene and run the test. When it’s done, you’ll see that the test passed.

Test run done

Congratulations! You successfully created your first passing unit test, and it verifies that spawned asteroids move down.

Note: Before you write unit tests of your own, you need to understand the implementation you’re testing. If you’re curious how the logic you’re testing works, review the code under Assets / Scripts.

Adding Tests to the Test Suite

The next test will test the game-over logic when the ship crashes into an asteroid. With TestSuite.cs open in the code editor, add the following test below the first unit test and save:

[UnityTest]
public IEnumerator GameOverOccursOnAsteroidCollision()
{
    GameObject gameGameObject =
        Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    game = gameGameObject.GetComponent<Game>();
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    //1
    asteroid.transform.position = game.GetShip().transform.position;
    //2
    yield return new WaitForSeconds(0.1f);

    //3
    Assert.True(game.isGameOver);

    Object.Destroy(game.gameObject);
}

You saw most of this code in the last test, but there are a few different things here:

  1. You’re forcing an asteroid and ship to crash by explicitly setting the asteroid to have the same position as the ship. This will force their hitboxes to collide and cause game over. If you’re curious how that code works, look at the Ship, Game and Asteroid files in the Scripts folder.
  2. A time-step is needed to ensure the Physics engine collision event fires so a 0.1 second wait is returned.
  3. This is a truth assertion, and it checks that the gameOver Boolean in the Game script has been set to true. The game code works with this flag being set to true when the ship is destroyed, so you’re testing to make sure this is set to true after the ship has been destroyed.

Go back to the Test Runner window, and you’ll now see this new unit test list there.

GameOverOccursOnASteroidCollision

This time, you’ll only run this one test instead of the whole test suite. Click GameOverOccursOnAsteroidCollision, then click the Run Selected button.

Run Selected button

And there you go — another test has passed. :]

Another run success

Setting Up and Tearing Down Phases

You might have noticed there’s some repeated code between the two tests where the Game’s GameObject is created and a reference to where the Game script is set:

GameObject gameGameObject =
        Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();

You’ll also notice it when the Game’s GameObject is destroyed:

Object.Destroy(game.gameObject);

It’s very common in testing to have this type of code — where you create the test environment and then clean it up at the end. But, it’s also good practice to keep your code DRY!

The Unity Test Framework provides two more attributes to help when it comes to running a unit test: the Setup phase and the Tear Down phase.

Any code inside of a Setup method will run before any unit test in that suite, and any code in the Tear Down method will run after every unit test in that suite.

It’s time to move this setup and tear down code into special methods. Open the code editor and add the following code to the top of the TestSuite file, just above the first [UnityTest] attribute:

[SetUp]
public void Setup()
{
    GameObject gameGameObject =
        Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    game = gameGameObject.GetComponent<Game>();
}

The SetUp attribute specifies that this method is called before each test is run.

Next, add the following method and save:

[TearDown]
public void Teardown()
{
    Object.Destroy(game.gameObject);
}

The TearDown attribute specifies that this method is called after each test is run.

With the setup and tear down code prepared, remove the lines of code that appear in these methods and replace them with the corresponding method calls. Your code will look like this:

public class TestSuite
{
    private Game game;

    [SetUp]
    public void Setup()
    {
        GameObject gameGameObject = 
            Object.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
        game = gameGameObject.GetComponent<Game>();
    }

    [TearDown]
    public void TearDown()
    {
        Object.Destroy(game.gameObject);
    }

    [UnityTest]
    public IEnumerator AsteroidsMoveDown()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        float initialYPos = asteroid.transform.position.y;
        yield return new WaitForSeconds(0.1f);
        Assert.Less(asteroid.transform.position.y, initialYPos);
    }

    [UnityTest]
    public IEnumerator GameOverOccursOnAsteroidCollision()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        asteroid.transform.position = game.GetShip().transform.position;
        yield return new WaitForSeconds(0.1f);
        Assert.True(game.isGameOver);
    }
}

Testing Game Over and Laser Fire

With the setup and tear down methods ready, it’s the perfect time to add more tests using them. The next test will verify that when the player clicks New Game, the gameOver bool is not true. Add the following test to the bottom of the file and save:

//1
[Test]
public void NewGameRestartsGame()
{
    //2
    game.isGameOver = true;
    game.NewGame();
    //3
    Assert.False(game.isGameOver);
}

This will look familiar, but here are a few things to notice:

  1. This test won’t require any time to pass, so it uses the standard [Test] attribute, and the method type is just void.
  2. This part of the code sets up this test to have the gameOver bool set to true. When the NewGame method is called, it will set this flag back to false.
  3. Here, you assert that the isGameOver bool is false, which should be the case after a new game is called.

Go back to the Test Runner, and you’ll see the new test NewGameRestartsGame is there. Run that test as you’ve done before and see that it passes:

NewGameRestartsGame

Asserting Laser Movement

The next test you add will test that the laser the ship fires moves up (similar to the first unit test you wrote). Open the TestSuite file in the editor. Add the following method and then save:

[UnityTest]
public IEnumerator LaserMovesUp()
{
      // 1
      GameObject laser = game.GetShip().SpawnLaser();
      // 2
      float initialYPos = laser.transform.position.y;
      yield return new WaitForSeconds(0.1f);
      // 3
      Assert.Greater(laser.transform.position.y, initialYPos);
}

Here’s what this code does:

  1. This gets a reference to a created laser spawned from the ship.
  2. The initial position is recorded so you can verify that it’s moving up.
  3. This assertion is just like the one in the AsteroidsMoveDown unit test, except now you’re asserting that the value is greater (indicating that the laser is moving up).

Save and go back to the Test Runner. Run the LaserMovesUp test and see that it passes:

LaserMovesUp

Now you’re firing through these test cases, so it’s time to add the last two tests and finish off this tutorial. :]