git:20260724.d4db756 to git:20260728.1bce424

82 added, 192 removed. Audit A to A.

---
name: unity-test-framework
description: "Author and run the Unity game-engine Test Framework (`com.unity.test-framework`, currently v1.8). Distinct from the ThrowTheSwitch Unity C testing library at throwtheswitch.org/unity - the two tools share only a name. Covers package install via Package Manager, the EditMode vs PlayMode split, the [Test] / [UnityTest] / [SetUp] / [TearDown] / [UnityPlatform] attributes, assembly-definition setup (Editor folder vs asmdef with `includePlatforms` / `optionalUnityReferences: [TestAssemblies]`), Test Runner window, command-line batch invocation with `-runTests` / `-testPlatform` / `-testResults` / `-testFilter` / `-testCategory`, NUnit 3.5 assertion API, and CI integration. Use when the unit under test is C# Unity code that needs to exercise the Unity runtime or editor."
metadata:
keywords: "unity, unity3d, test-framework, utf, edit-mode, play-mode, nunit, csharp, game-engine"
---
# unity-test-framework
## Overview
**Disambiguation up front.** This skill covers the
**Unity game-engine Test Framework** - the official Unity package
`com.unity.test-framework` documented at
- [docs.unity3d.com/Packages/com.unity.test-framework@latest](https://docs.unity3d.com/Packages/com.unity.test-framework@latest)
- (currently version 1.8 per the canonical-version redirect on
- that page; manual mirrored at the
- [v1.4 manual snapshot](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html)
- for stable URLs).
+ [docs.unity3d.com/Packages/com.unity.test-framework@latest](https://docs.unity3d.com/Packages/com.unity.test-framework@latest).
+ It is **distinct from the ThrowTheSwitch Unity C testing library at
+ [throwtheswitch.org/unity](https://www.throwtheswitch.org/unity)** (a
+ single-C-file unit-test framework for bare-metal MCUs). The two tools share
+ only a name; they have unrelated origins, APIs, and consumers. **For the C
+ unit-test library, see the sibling skill `unity-test-framework-c`** in the
+ qa-embedded plugin.
- It is **distinct from the ThrowTheSwitch Unity C testing library
- at [throwtheswitch.org/unity](https://www.throwtheswitch.org/unity)**
- (a single-C-file unit-test framework for bare-metal MCUs). The
- two tools share only a name; they have unrelated origins, APIs,
- and consumers. **For the C unit-test library, see the sibling
- skill `unity-test-framework-c`** in the qa-embedded plugin.
+ UTF is built on **NUnit 3.5** and supports both **Edit Mode** and **Play Mode**
+ across Standalone, Android, and iOS target platforms (per the
+ [package overview](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html)).
- Per the
- [package overview](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html),
- UTF is built on **NUnit 3.5** and supports both **Edit Mode** and
- **Play Mode** across Standalone, Android, and iOS target platforms.
+ **Versioning.** UTF is currently **v1.8**; the stable doc links below pin to
+ the **v1.4** manual snapshot for durable URLs. Both describe the same package.
Composes with:
- `game-test-categories-reference`
for what category of test each Unity test belongs to (functional
/ performance / etc.).
- `platform-cert-overview-reference`
for cert-gated requirements UTF tests should cover (XR-001
Title Stability, XR-074 Service Loss, XR-115 Controller add /
remove).
- `multiplayer-state-machine-coverage`
and
`gameplay-recording-replay`
for state-machine and replay-driven test patterns inside UTF
PlayMode tests.
## When to use
- Unit under test is **C# Unity code** that needs the Unity
runtime (`MonoBehaviour`, `GameObject`, coroutines, scenes) or
editor (`AssetDatabase`, `EditorWindow`) APIs available.
- You want CI-runnable tests that integrate with Unity Cloud
Build, GitHub Actions, or other CI via Unity's batch-mode CLI.
- You need both isolated logic tests (EditMode) and integration
tests that exercise scene lifecycle (PlayMode) in one harness.
For C unit tests on the firmware side of a hybrid game build,
use `unity-test-framework-c` instead.
## Authoring
### Install via Package Manager
- Per the
- [package overview](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html),
- UTF "is shipped with the Unity Editor and should be automatically
- included in any project created with Unity 2019.2 or later". For
- manual install, open Package Manager → `+` → "Add package by
- name…" → `com.unity.test-framework`.
+ UTF "is shipped with the Unity Editor and should be automatically included in
+ any project created with Unity 2019.2 or later" (per the package overview). For
+ manual install, open Package Manager → `+` → "Add package by name…" →
+ `com.unity.test-framework`.
- The Test Runner window is at **Window → General → Test Runner**
- (may be Window → Test Runner depending on Editor version).
+ The Test Runner window is at **Window → General → Test Runner** (may be
+ Window → Test Runner depending on Editor version).
### EditMode tests
Per the
- [Edit Mode vs Play Mode tests page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html):
- Edit Mode tests "are only run in the Unity Editor and have access to
- the Editor code in addition to the game code".
+ [Edit Mode vs Play Mode tests page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html),
+ Edit Mode tests "are only run in the Unity Editor and have access to the Editor
+ code in addition to the game code".
Layout (two options):
- - **Editor folder (legacy):** place tests under any folder named
- `Editor/`. Unity auto-scopes them to the Editor platform.
+ - **Editor folder (legacy):** place tests under any folder named `Editor/`.
+ Unity auto-scopes them to the Editor platform.
- **Assembly definition (recommended):** create an `.asmdef` with
`"includePlatforms": ["Editor"]`.
Minimal example:
```csharp
using NUnit.Framework;
using UnityEngine;
namespace MyGame.Tests.EditMode
{
public class HealthComponentTests
{
[Test]
public void Damage_DeductsCorrectAmount()
{
var go = new GameObject();
var health = go.AddComponent<HealthComponent>();
health.Initialize(maxHealth: 100);
health.ApplyDamage(35);
Assert.AreEqual(65, health.Current);
}
[TearDown]
public void Cleanup()
{
// EditMode tests must clean up created GameObjects;
// they don't auto-tear-down between cases.
}
}
}
```
- Per the
- [edit-mode-vs-play-mode-tests page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html),
- the docs "recommend using the NUnit `Test` attribute rather than
- `UnityTest`, unless you need to yield special instructions, in
- Edit Mode, or if you need to skip a frame or wait for a certain
- amount of time in Play Mode".
+ The docs recommend the NUnit `Test` attribute rather than `UnityTest` unless
+ you need to yield special instructions in Edit Mode, or skip a frame / wait for
+ an amount of time in Play Mode.
### PlayMode tests
- Per the same page, PlayMode tests "execute as coroutines within
- the game runtime and can run standalone in a Player or within the
- Editor".
-
- Assembly definition for PlayMode tests:
+ PlayMode tests "execute as coroutines within the game runtime and can run
+ standalone in a Player or within the Editor". Assembly definition:
```json
{
"name": "MyGame.Tests.PlayMode",
"references": ["MyGame.Runtime"],
"optionalUnityReferences": ["TestAssemblies"],
"includePlatforms": []
}
```
- Per the
- [edit-mode-vs-play-mode-tests page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html):
- the `.asmdef` must reference the code under test (`"references":
- ["NewAssembly"]`), optionally include `"optionalUnityReferences":
- ["TestAssemblies"]`, and leave `"includePlatforms": []` empty to
- allow multiple target platforms.
+ Per the edit-mode-vs-play-mode docs, the `.asmdef` must reference the code
+ under test (`"references": ["NewAssembly"]`), optionally include
+ `"optionalUnityReferences": ["TestAssemblies"]`, and leave
+ `"includePlatforms": []` empty to allow multiple target platforms.
Minimal example:
```csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace MyGame.Tests.PlayMode
{
public class EnemyAITests
{
[UnityTest]
public IEnumerator Enemy_PursuesPlayer_WithinRange()
{
var player = new GameObject("Player");
var enemy = Object.Instantiate(Resources.Load<GameObject>("Enemy"));
enemy.transform.position = new Vector3(10f, 0f, 0f);
yield return new WaitForSeconds(2f);
var distance = Vector3.Distance(player.transform.position,
enemy.transform.position);
Assert.Less(distance, 5f, "Enemy did not close on player");
}
}
}
```
- The `[UnityTest]` attribute, returning `IEnumerator`, lets the
- test yield frames (`yield return null`), seconds
- (`yield return new WaitForSeconds(2f)`), or custom yield
- instructions - necessary for any frame-driven behaviour.
-
- ### Attribute cheatsheet
+ The `[UnityTest]` attribute, returning `IEnumerator`, lets the test yield
+ frames (`yield return null`), seconds (`yield return new WaitForSeconds(2f)`),
+ or custom yield instructions - necessary for any frame-driven behaviour.
- Per the [v1.4 manual index](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html)
- and NUnit 3.5 docs the framework wraps:
+ ### Attributes
- | Attribute | Purpose | Source |
- |---|---|---|
- | `[Test]` | Plain NUnit test - synchronous | NUnit; recommended default per edit-mode-vs-play-mode docs |
- | `[UnityTest]` | Coroutine-style test that can yield frames / seconds in PlayMode or skip frames in EditMode | UTF-specific |
- | `[SetUp]` / `[TearDown]` | Per-test fixture setup / cleanup | NUnit |
- | `[OneTimeSetUp]` / `[OneTimeTearDown]` | Once-per-fixture setup / cleanup | NUnit |
- | `[TestFixture]` | Marks a class as containing tests (optional in NUnit 3) | NUnit |
- | `[Category("Smoke")]` | Tag a test for filtering | NUnit; selectable via CLI `-testCategory` |
- | `[UnityPlatform(RuntimePlatform.WindowsPlayer)]` | Restrict test to specific runtime platforms | UTF-specific |
- | `[ValueSource(nameof(MyCases))]` | Parameterised inputs | NUnit; `ValueSource` is supported per the [v1.4 manual](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html) (other parameterised attributes have known limitations - see Limitations section) |
+ The common attributes are `[Test]` (synchronous NUnit), `[UnityTest]`
+ (coroutine, yields frames/seconds), `[SetUp]` / `[TearDown]`,
+ `[OneTimeSetUp]` / `[OneTimeTearDown]`, `[Category("Smoke")]` (CLI-selectable),
+ and `[UnityPlatform(...)]`. The full cheatsheet with sources and the
+ `ValueSource` parameterisation note is in
+ [references/attributes.md](references/attributes.md).
### NUnit assertion APIs
- Per the [package overview](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html),
- UTF is built on **NUnit 3.5**. The full NUnit 3 assertion model
- applies - `Assert.AreEqual`, `Assert.Throws<T>(() => ...)`,
- `Assert.That(actual, Is.EqualTo(expected).Within(0.01f))`, etc.
- Per Unity's recommendation in the edit-mode-vs-play-mode docs,
- prefer the NUnit `Test` attribute over `UnityTest` "unless you
- need to yield special instructions".
+ UTF is built on NUnit 3.5, so the full NUnit 3 assertion model applies -
+ `Assert.AreEqual`, `Assert.Throws<T>(() => ...)`,
+ `Assert.That(actual, Is.EqualTo(expected).Within(0.01f))`, etc. Prefer the
+ NUnit `Test` attribute over `UnityTest` unless you need to yield special
+ instructions.
## Running
### From the Test Runner window
**Window → General → Test Runner** (path may be Window → Test
Runner in older Editor versions). The window shows two tabs:
- **EditMode** - synchronous tests executed against the editor
domain.
- **PlayMode** - tests that enter and exit play mode for each
fixture.
Click **Run All**, **Run Selected**, or right-click a fixture →
**Run** to execute. Results display inline with stack traces on
failure.
### From the command line (batch mode)
- Per the
- [command-line reference](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html),
- Unity exposes these flags:
-
- | Flag | Effect |
- |---|---|
- | `-runTests` | "Executes tests within the project." |
- | `-batchmode` | "Removes the need for manual user inputs when running tests from the command line." |
- | `-projectPath <path>` | Project root. |
- | `-testResults <path>` | "Designates where Unity stores the result file (XML format per NUnit standards). If unspecified, results are saved in the project root." |
- | `-testPlatform EditMode\|PlayMode\|<BuildTarget>` | "Default: EditMode if not specified." `BuildTarget` (e.g. `StandaloneWindows64`, `Android`) runs tests on a built player for that platform. |
- | `-testFilter "Pattern"` | "Accepts a semicolon-separated list or regex pattern to match test names. Supports negation with `!`." |
- | `-testCategory "Smoke;Critical"` | "Accepts a semicolon-separated list or regex pattern for category matching. Also supports negation with `!`." |
- | `-assemblyNames "MyGame.Tests.PlayMode"` | Limit to specific test assemblies. |
- | `-runSynchronously` | Run on the main thread synchronously (EditMode only). |
- | `-orderedTestListFile`, `-randomOrderSeed`, `-retry`, `-repeat`, `-playerHeartbeatTimeout`, `-testSettingsFile` | "Control test execution order, failure handling, timing, and settings configuration" per the same page. |
-
- Full example invocation (Linux / macOS):
+ Invoke Unity in batch mode and treat the `-testResults` XML as the source of
+ truth (Unity has no common exit-code definition):
```bash
Unity \
-batchmode \
-projectPath "$PWD" \
-runTests \
-testPlatform PlayMode \
-testResults artifacts/playmode-results.xml \
-testCategory "Smoke" \
-logFile artifacts/unity.log
```
- Windows equivalent: invoke
- `"C:\Program Files\Unity\Hub\Editor\<version>\Editor\Unity.exe"`
- with the same flags.
-
- ### Exit-code caveat
-
- Per the
- [command-line reference page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html):
- "There is currently no common definition for exit codes reported
- by individual Unity components under test. Error messages and
- stack traces in results provide better diagnostic information."
- Treat the produced `-testResults` XML as the source of truth in
- CI rather than process exit code.
+ The full batch-mode flag table (`-testFilter`, `-testPlatform`,
+ `-assemblyNames`, ordering / retry flags), the Windows invocation, and the
+ exit-code caveat are in [references/cli.md](references/cli.md).
## Parsing results
- The XML written to `-testResults <path>` is **NUnit 3 result
- format**. The CI pipeline must parse it to surface failures.
-
- Top-level structure (per NUnit 3):
-
- ```xml
- <test-run id="2" testcasecount="42" result="Failed" total="42"
- passed="40" failed="1" inconclusive="0" skipped="1">
- <test-suite ...>
- <test-case fullname="MyGame.Tests.HealthTests.Damage_DeductsCorrectAmount"
- result="Passed" duration="0.012"/>
- <test-case fullname="MyGame.Tests.EnemyAITests.Enemy_PursuesPlayer_WithinRange"
- result="Failed" duration="2.34">
- <failure>
- <message><![CDATA[Enemy did not close on player]]></message>
- <stack-trace><![CDATA[at MyGame.Tests.EnemyAITests...]]></stack-trace>
- </failure>
- </test-case>
- </test-suite>
- </test-run>
- ```
-
- Surface `result="Failed"` at the `<test-run>` level for the
- overall pass / fail, then enumerate `<test-case result="Failed">`
- for per-test detail. The schema is NUnit-canonical - parsers exist
- for JUnit consumers (e.g., `nunit-junit-xml` converters) for tools
- that expect JUnit XML.
+ The `-testResults` XML is **NUnit 3 result format**. Surface `result="Failed"`
+ at the `<test-run>` level for the overall verdict, then enumerate
+ `<test-case result="Failed">` for per-test detail; `nunit-junit-xml`
+ converters exist for JUnit consumers. The full schema example is in
+ [references/results-and-ci.md](references/results-and-ci.md).
## CI integration
- GitHub Actions example using `game-ci/unity-test-runner`:
-
- ```yaml
- jobs:
- unity-tests:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- lfs: true
- - uses: actions/cache@v4
- with:
- path: Library
- key: Library-${{ github.sha }}
- restore-keys: Library-
- - uses: game-ci/unity-test-runner@v4
- env:
- UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
- with:
- testMode: all # editmode + playmode
- artifactsPath: artifacts
- coverageOptions: "generateAdditionalMetrics;generateHtmlReport"
- - uses: actions/upload-artifact@v4
- if: always()
- with:
- name: unity-test-results
- path: artifacts/**/*.xml
- ```
-
- Bare-CLI equivalent - invoke Unity directly with the flags from
- the
- [command-line reference](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html)
- and treat the `-testResults` XML as source of truth.
-
- Cache the `Library/` folder across runs - Unity re-imports all
- assets without it, adding 5 - 15 min per CI run.
+ Run UTF in CI via `game-ci/unity-test-runner` (or the bare batch-mode CLI),
+ upload the `-testResults` XML as an artifact, and cache the `Library/` folder
+ across runs - Unity re-imports all assets without it, adding 5 - 15 min per
+ run. A complete GitHub Actions workflow is in
+ [references/results-and-ci.md](references/results-and-ci.md).
## Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
- | Using `[UnityTest]` everywhere | Slower than `[Test]` because each test enters play mode | Per [edit-mode-vs-play-mode docs](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html), use `[Test]` unless you need to yield |
- | Forgetting `optionalUnityReferences: ["TestAssemblies"]` | Test assembly isn't picked up by Test Runner | Add per the asmdef block in [edit-mode-vs-play-mode-tests](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html) |
- | Test asmdef references production asmdef but production has no public types | Tests can't compile | Use `InternalsVisibleTo` attribute on production asmdef or expose minimal public surface |
+ | Using `[UnityTest]` everywhere | Slower than `[Test]` because each test enters play mode | Use `[Test]` unless you need to yield (per the edit-mode-vs-play-mode docs) |
+ | Forgetting `optionalUnityReferences: ["TestAssemblies"]` | Test assembly isn't picked up by Test Runner | Add it per the PlayMode asmdef block above |
+ | Test asmdef references production asmdef but production has no public types | Tests can't compile | Use `InternalsVisibleTo` on production asmdef or expose minimal public surface |
| Not cleaning up `GameObject`s between EditMode tests | Cross-test contamination | `[TearDown]` `Object.DestroyImmediate(go)` on every fixture |
- | Trusting Unity process exit code in CI | Per [command-line reference](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html), "no common definition for exit codes" | Parse `-testResults` XML in CI |
+ | Trusting Unity process exit code in CI | No common definition for exit codes | Parse `-testResults` XML in CI |
| Skipping the `Library/` cache | 5 - 15 min asset reimport per CI run | `actions/cache@v4` with the `Library/` path |
| Confusing this skill with ThrowTheSwitch Unity (C) | Different tool with the same name | See `unity-test-framework-c` |
## Limitations
- Per the
- [v1.4 manual index page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/index.html),
- known constraints:
+ Known constraints (per the v1.4 manual index):
- - **No WSA platform support for `UnityTest`** - Windows Store
- Apps cannot run `[UnityTest]` coroutine-style tests.
- - **Parameterised tests unsupported (except `ValueSource`)** -
- NUnit `[TestCase]`, `[TestCaseSource]` have historical caveats
- in UTF.
- - **`[Repeat]` attribute incompatibility** - listed as a known
- limitation.
+ - **No WSA platform support for `UnityTest`** - Windows Store Apps cannot run
+ `[UnityTest]` coroutine-style tests.
+ - **Parameterised tests unsupported (except `ValueSource`)** - NUnit
+ `[TestCase]`, `[TestCaseSource]` have historical caveats in UTF.
+ - **`[Repeat]` attribute incompatibility** - listed as a known limitation.
- **Nested test fixtures cannot run from the Editor UI.**
- - **`[Retry]` attribute causes `InvalidCastException` in PlayMode
- tests.**
+ - **`[Retry]` attribute causes `InvalidCastException` in PlayMode tests.**
Other practical limitations:
- - **Asset Database in PlayMode is sandboxed.** PlayMode tests
- cannot freely create / mutate assets the way EditMode tests can.
+ - **Asset Database in PlayMode is sandboxed.** PlayMode tests cannot freely
+ create / mutate assets the way EditMode tests can.
- **Coverage** requires the separate `com.unity.testtools.codecoverage`
package; not included by default.
- **License gating on CI** - Unity batch-mode CI requires either a
- Personal/Plus license seat (`-username -password -serial`) or a
- manual `.ulf` license file; consult Unity's documentation for
- the current activation flow before standing up CI.
- - **No public common exit-code definition** per the
- [command-line reference](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/reference-command-line.html);
+ Personal/Plus license seat (`-username -password -serial`) or a manual `.ulf`
+ license file; consult Unity's documentation for the current activation flow
+ before standing up CI.
+ - **No public common exit-code definition** (per the command-line reference);
parse XML, don't rely on exit code.
- - **EditMode test that enters PlayMode** can do so per the
- [edit-mode-vs-play-mode-tests page](https://docs.unity3d.com/Packages/com.unity.test-framework@1.4/manual/edit-mode-vs-play-mode-tests.html)
- ("Edit Mode tests can control entering and exiting Play Mode
- from your test") but cross-mode tests are notoriously flaky;
- prefer keeping the two modes' tests separate.
+ - **EditMode test that enters PlayMode** is supported ("Edit Mode tests can
+ control entering and exiting Play Mode from your test") but cross-mode tests
+ are notoriously flaky; prefer keeping the two modes' tests separate.