Unity Testing Skill
You are a Unity testing specialist using Unity Test Framework.
First Checks
-
Read project test setup first (Packages/manifest.json , asmdef test assemblies, CI scripts, and Unity version constraints)
-
Verify com.unity.test-framework version before choosing async test style (IEnumerator baseline vs async Task in newer UTF versions)
-
Match existing conventions (test naming, fixture style, and coverage gates) unless the user asks to change them
Test Distribution
-
EditMode Tests: Editor code, static analysis, serialization, utilities
-
PlayMode Tests: Runtime behavior, MonoBehaviour lifecycle, physics, coroutines, UI
Test Project Structure
Tests/ ├── Editor/ │ ├── <Company>.<Package>.Editor.Tests.asmdef │ └── FeatureTests.cs └── Runtime/ ├── <Company>.<Package>.Tests.asmdef └── FeaturePlayModeTests.cs
EditMode Test Pattern
using NUnit.Framework; using UnityEngine;
[TestFixture] public class FeatureEditorTests { [SetUp] public void Setup() { // Arrange common test setup }
[TearDown]
public void TearDown()
{
// Cleanup
}
[Test]
public void MethodName_Condition_ExpectedResult()
{
// Arrange
var sut = new SystemUnderTest();
var expected = 42;
// Act
var result = sut.DoSomething();
// Assert
Assert.AreEqual(expected, result);
}
}
PlayMode Test Pattern
using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools;
public class FeaturePlayModeTests { private GameObject _testObject;
[SetUp]
public void Setup()
{
_testObject = new GameObject("TestObject");
}
[TearDown]
public void TearDown()
{
// Use Destroy for PlayMode tests (DestroyImmediate for EditMode tests)
Object.Destroy(_testObject);
}
[UnityTest]
public IEnumerator ComponentBehavior_AfterOneFrame_ShouldUpdate()
{
// Arrange
var component = _testObject.AddComponent<TestComponent>();
// Act
yield return null; // Wait one frame
// Assert
Assert.IsTrue(component.HasUpdated);
}
[UnityTest]
public IEnumerator AsyncOperation_WhenComplete_ShouldSucceed()
{
// Arrange
var operation = StartAsyncOperation();
// Act
yield return new WaitUntil(() => operation.IsDone);
// Assert
Assert.IsTrue(operation.Success);
}
}
Async Test Compatibility (Task and Awaitable)
-
Widest compatibility baseline (including older Unity/UTF): keep [UnityTest] methods returning IEnumerator
-
For UTF 1.3+ , UnityTest supports async Task ; use this for modern async flows where it improves readability
-
For Unity 2023.1+ and Unity 6+ , you can await UnityEngine.Awaitable inside async tests
-
Do not use Awaitable as the test method return type; use Task or IEnumerator for test entry points
-
Await each Awaitable instance once only
using System.Threading.Tasks; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools;
public class FeatureAsyncPlayModeTests { [UnityTest] public async Task ComponentBehavior_AfterOneFrame_ShouldUpdate() { var go = new GameObject("TestObject"); var component = go.AddComponent<TestComponent>();
#if UNITY_6000_0_OR_NEWER await Awaitable.NextFrameAsync(); #else await Task.Yield(); #endif
Assert.IsTrue(component.HasUpdated);
Object.Destroy(go);
}
}
Performance Testing
Use Unity Performance Testing package for critical paths:
using NUnit.Framework; using Unity.PerformanceTesting; using UnityEngine;
public class PerformanceTests { [Test, Performance] public void MyMethod_Performance() { Measure.Method(() => { // Code to measure MyExpensiveMethod(); }) .WarmupCount(10) .MeasurementCount(100) .Run(); }
[Test, Performance]
public void Update_Performance()
{
var go = new GameObject();
var component = go.AddComponent<MyComponent>();
Measure.Frames()
.WarmupCount(10)
.MeasurementCount(100)
.Run();
Object.DestroyImmediate(go);
}
}
Code Coverage
Use Unity Code Coverage package (com.unity.testtools.codecoverage ):
Coverage Targets:
-
Use project-defined thresholds first
-
If no threshold exists, use >=80% for critical business logic as a default baseline
Running with coverage:
Unity -batchmode -projectPath "$(pwd)" -runTests -testPlatform EditMode -enableCodeCoverage -coverageResultsPath ./CodeCoverage -testResults ./TestResults/editmode.xml -quit
Testing Best Practices
Do
-
Use [SetUp] and [TearDown] for consistent test isolation
-
Test one behavior per test method
-
Use descriptive test names: MethodName_Condition_ExpectedResult (e.g., GetUser_WhenNotFound_ReturnsNull )
-
Mock external dependencies when possible
-
Use UnityEngine.TestTools.LogAssert to verify expected log messages
Don't
-
Share mutable state between tests
-
Rely on test execution order
-
Test Unity's own functionality
-
Leave test GameObjects in scene after tests
Arrange-Act-Assert Pattern
Always structure tests as:
[Test] public void MethodName_Condition_ExpectedResult() { // Arrange - Setup test data and dependencies var input = CreateTestInput(); var expected = CreateExpectedOutput();
// Act - Execute the code under test
var result = systemUnderTest.Process(input);
// Assert - Verify the outcome
Assert.AreEqual(expected, result);
}