I'm Joris "Interface" de Gruyter. Welcome To My

Code Crib

Unity Unit Testing Beyond the Basics

Nov 17, 2022
Filed under: #tech #unity #gamedev

One of the things that’s great about Unity is that you can find pretty much whatever information you need online. Docs aren’t too bad, but the community has done forum posts, blogs, YouTubes and all sorts of content over the years. One thing I just haven’t been able to find though, is any information on using unit testing beyond just the basics of getting set up. So, basically, all that was left is trial & error. Here’s the questions I answered for myself.

To be clear, I’m not going to get into pure unit testing. Unit tests are unit tests. What I’m talking about is the PlayMode tests. I feel these are basically useful for cetain integrated tests. Anyway, here’s some answers to the questions I was dealing with.

Since version likely matters here - at the time of writing this I was using Unity 2021.3.5f1 and Test Framework package 1.1.31 (2019.2.0a10) for my project.

I would love to spend a bit more time and actually give an example project to provide more context for those new to testing. But for now I want to at least get this info out there for anyone else looking for it.

Do Other nUnit Test Features Work?

You can do multiple data runs on one unit test. Put an argument in your test method, and use the [TestCase(value)] attribute, where value is a value you wish to pass in the method. You can put multiple attributes to test different values into the same test method. Just do an internet search for nUnit and TestCase attribute to see how it’s used.

Since coroutines return a value (IEnumerator), the TestCase attribute also requires you specify a return value. I just used ExpectedResult = null which seems to be working just fine.

Also, you’re supposed to use [UnitTest] or [Test]. The difference is supposed to be that [UnitTest] runs your test as a coroutine so you can yield wait instructions etc. It seemed when using the TestCase attribute I didn’t need to specify UnitTest or Test, and yielding wait instructions still worked…

You can use [SetUp], [OneTimeSetup], [TearDown], etc. There are also Unity-specific versions like [UnitySetUp]. I didn’t need to use those Unity-specific ones, but now that I think about it maybe it’s tied into the [UnitTest] attribute if you used that?

So, more exact experimenting would be nice to figure this out. YMMV

Scenes in PlayMode tests

When you run play mode tests they always start in an entirely blank scene (with camera). Doesn’t matter what scene you have currently loaded in the editor.

You can load scenes as part of the test. The problem with this is they’re supposed to be in the scenes list of your build settings. If you’re making scenes just for testing, this is likely undesirable. I saw some posts online about messing with build settings from code to add a scene at runtime without changing your actual settings, or maybe you can clean it up or something? I ended up not caring as what I’m testing is all in prefabs I can instantiate. So I just use the blank scene.

GameObjects, Prefabs and Serialize Properties

Instantiating prefabs or game objects is easy enough. But if your properties are private and you’re using the [SerializeField] property to expost things to the editor, how do you provide values? Also, hard-coding preset values may be a lot of hardcoded values to write. So, I used scriptable objects. The scriptable objects can have values, references to prefabs, etc. You can create multiple assets for different tests that have different values for the same scriptable object so you can test different things… Since I’m running these tests inside the editor, I just use AssetDatabase.LoadAssetAtPath to load my scriptable object. Easy enough.

Now, every game object you instantiate is in the scene. The scene (unless you load one) does not “clean up” between different tests. So you need to cleanup game objects yourself. I created a utility class that i use to instantiate game objects, which then keeps a list so it can destroy all of them. You can do this with setup/teardown. Or you can implement IDisposable and create a using scope and get all fancy to cleanup game objects that way.

If you instantiate any game objects that have code in Start(), you can yield return after instantiating to let a frame update run through all the code as expected.

 

There is no comment section here, but I would love to hear your thoughts! Get in touch!

Blog Links

Blog Post Collections

Recent Posts