Unity: Unit testing

Testing is an integral part of a software development life cycle. It helps to make your code robust and maintainable whilst ensuring that unavoidable refactoring doesn’t break the application. But as a small game studio or a lonely indie dev you might never get much needed time and resources to write a couple of tests. Nevertheless, in the long run testing, be it unit, integration or user-acceptance, will undoubtedly greatly contribute to the project well-being.

Unity, as one of the most popular game dev frameworks, provides testing support via Unity Test Framework package, which in turn uses NUnit library.

Prerequisites

I’m using Unity 2021.2.13. This tutorial will re-use the code from my previous one — Unity: Create your own Wordle in under 24 hours. Make sure to check it beforehand.

As always, project sources are available at GitHub, find the link at the end of the page.

There are only 2 parts in this tutorial: Unity setup and Writing tests.

Part 1. Unity setup

First and foremost, we should prepare Unity to work with the tests. I personally found it to be slightly more complicated compared to other testing frameworks.

Make sure that the Unity Test Framework package is installed.
Go to Window → Package Manager and locate Test Framework there:

If it is not there, check out this official Unity guide about Package Manager and packages installation process.

Next, we will open a Test Runner panel.
Go to Window → General → Test Runner.

Press the Create EditMode Test Assembly Folder button.

It will create a new folder Tests under Assets with a file Tests — that’s an assembly definition file. In layman’s terms that’s the file that tells Unity where the test file dependencies are located. As you probably noticed already, the Tests folder is not placed next to the production code. You can read more about it here.

With Tests assembly definition file selected in the Project panel you can already create a dummy Test script

I will go ahead and create the first test file for the LetterControl script:

Unfortunately, this script won’t even compile as it cannot resolve the LetterControl reference.

In order to make the script visible for test we will need to create another assembly definition file. That one will reference our production code.
Go to the Assets → Scripts folder and create it there.

Now go back to the Tests assembly definition and reference it there:

You might be thinking that the test should work now. What happened instead though, is that other production scripts are no longer compiling! The reason for it is simple — the newly created Game Assembly Definition is missing a few references — TextMeshPro and DOTween.

Resolving TextMeshPro compilation issues is rather straightforward — it is already available in Unity, so we should just add it to Game and Tests:

Note! Check out this post to learn more about why TextMeshPro needs an explicit reference

At the time of writing there is no pre-created assembly definition for DOTween, thus we will create one ourselves.

Go to Assets → Plugins → Demigiant → DOTween and create it there:

Now add it to the Game definition assembly:

And finally the code is now compiling and the test can be run!

It is failing at the moment and that’s something we will be looking next.

Part 2. Writing tests

If we check the LetterControl script we will see that it extends MonoBehaviour, and all of the instances extending it must be instantiated with GameObject.AddComponent<T>() instead of the new keyword.

Let’s apply the change and run the test again:

Now it passes successfully!

Before we jump into writing the test cases let me take a break and say a few words about the tests structure. Similarly to most of the Unit test frameworks out there, NUnit has its own attributes for:

  • Setting up the test — [SetUp] — that’s the step performed just before each test method is called. Its purpose is to prepare the required data for the tests.
  • Running the test — [Test] — that’s the test itself.
  • Running the Unity test [UnityTest] — this is the framework extension. This type of unit test allows you to skip a frame from within a test (so background tasks can finish) or give certain commands to the Unity Editor, such as performing a domain reload or entering Play Mode from an Edit Mode test.
  • Cleaning up after the test execution — [TearDown] — that’s the step performed after each test method. It is used to reset the state of objects so that tests are not affecting each other.

In order to create an actual test case you will be using Assert class. There are numerous methods available there, such as NotNull, IsTrue, IsEmpty and many more. The names of those are quite intuitive, so you should be able to find a suitable one.

Looking at the LetterControl script once again we can see that there are 3 public methods to be tested: GetLetter, EnterLetter and ChangeColorControl. We can also see that the script has 3 components attached: Image, TextMeshProUGUI and Controls.

Note! Ideally, internal components should be mocked. Unfortunately, there is no up-to-date mocking framework available for Unity at the moment.

First, let’s set up the required components — we will create a method with [SetUp] attribute:

As you can see, it is a rather tedious setup as we have to make sure that all the components are added to the correct places in the hierarchy. It is important not to forget to call Awake() after the preparation is done as it won’t be called automatically by the test.

Now when setup is done, we can finally create the first real test case:

Run it in Unity to see the green checkmark meaning that the test has passed successfully!

To keep this tutorial short, only one more test will be created — a tricky one, for the ChangeColorControl method. Looking at the code you can see that the change of a color doesn’t happen immediately but after a delay — that’s needed to have a beautiful transition animation. But how should we test it then? As it was mentioned before the testing framework provides a handy attribute — [UnityTest] — that can be used to halt a test execution so the background execution finishes.

Since we need to pause the test for a given amount of seconds we must switch the test suite to PlayMode from the current EditMode. Select the Tests assembly definition and tick the current running platform (WedGL in this case).

The test will make use of WaitForSeconds yield instruction. One important note here — we cannot compare two Color objects directly using AreEqual method unfortunately as the Color class holds its internal values as floats. And as doubles and floats cannot express every numerical value, they are using approximations to represent the value. Thus we should compare each of the Color fields using Mathf.Approximately:

One last run and now we see two checkmarks:

Afterwards

Well done finishing the tutorial! Now you know how to write the tests in Unity. My personal take on the current state of testing is that it is a rather complex process. Compared to other testing frameworks I’ve used (e.g. JUnit, React Testing Library) Unity testing framework is definitely lagging behind in simplicity and ease of running. Nevertheless, it is a powerful tool that shouldn’t be neglected when working on a big project with a distributed team of developers.

Should you have any questions please leave them in the comments section below.

The project source files can be found at this GitHub repository. Check this commit for all the changes applied in scope of this tutorial.

I’ve used the described approach in Categorle, a Wordle variant where you are guessing a word 4–7 letters long in a given category.

For the most comprehensive list of Wordle-like games and resources online be sure to check out this page.

Support

If you like the content you read and want to support the author— thank you very much!
Here is my Ethereum wallet for tips:
0xB34C2BcE674104a7ca1ECEbF76d21fE1099132F0

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store