unity-testing

SKILL.md

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);
}
Weekly Installs
4
GitHub Stars
2
First Seen
13 days ago
Installed on
gemini-cli4
opencode4
codebuddy4
github-copilot4
codex4
kimi-cli4