# 3. UnityTest Async Tests and Domain Reloads
Part of [[Unity Test Framework]]. Previous: [[2. Edit Mode Tests and NUnit]]. Next: [[4. Play Mode Tests and Scene Fixtures]].
> [!note] Verification status
> `[UnityTest]` (returns `IEnumerator`), `[UnitySetUp]` / `[UnityTearDown]`, `[UnityOneTimeSetUp]` / `[UnityOneTimeTearDown]` (added in Test Framework 1.5.0), async `Task` tests (added in 1.3.0), `MaxTime` on async and UnityTest methods (1.4.5), and yield instructions (`EnterPlayMode`, `ExitPlayMode`, `RecompileScripts`, `WaitForDomainReload`) are documented in the Test Framework 1.7 API and changelog. **Awaitable is not documented as a supported test method return type** — do not return `Awaitable` from a test; use `Task` or `IEnumerator`. **Code not compiled in Unity.**
## 3.1 When a regular `[Test]` is not enough
A `[Test]` method is synchronous. The Test Runner calls it, and when it returns (or throws), the test is over. That is perfect for `IntBag`. It is not enough when you need to:
- Wait a frame (or several) for `Start`, a physics step, or a UI bind to finish
- Enter or exit Play Mode from an Edit Mode test
- Recompile scripts and survive the domain reload
- `await` an async production API
Unity gives you two styles: a coroutine-like `[UnityTest]` and an async `Task` `[Test]`.
## 3.2 `[UnityTest]`
The method must return `IEnumerator`. In **Play Mode** it runs as a coroutine. In **Edit Mode** the Test Runner pumps it on `EditorApplication.update`.
```csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class CounterBehaviourTests
{
[UnityTest]
public IEnumerator Tick_AfterOneFrame_Increments()
{
var go = new GameObject("counter");
var counter = go.AddComponent<FrameCounter>();
yield return null; // one frame (or one Editor update in Edit Mode)
Assert.GreaterOrEqual(counter.Ticks, 1);
Object.DestroyImmediate(go); // DestroyImmediate in Edit Mode; Destroy is fine in Play Mode
}
}
```
A stand-in production component:
```csharp
using UnityEngine;
public sealed class FrameCounter : MonoBehaviour
{
public int Ticks { get; private set; }
void Update()
{
Ticks++;
}
}
```
If you yield `WaitForSeconds` or `WaitForEndOfFrame`, you are almost certainly in Play Mode. `WaitForEndOfFrame` in a batchmode Play Mode run was a historical hang (fixed in 1.4.5, but still a reason to prefer `yield return null` unless you really need the end of the frame).
`[UnitySetUp]` and `[UnityTearDown]` are the yielding versions of `[SetUp]` / `[TearDown]`. Use them to load a fixture scene or enter Play Mode once per test. `[UnityOneTimeSetUp]` / `[UnityOneTimeTearDown]` (Test Framework 1.5.0) run once per fixture and may also yield.
```csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class PlayModeEntryTests
{
[UnitySetUp]
public IEnumerator UnitySetUp()
{
yield return new EnterPlayMode();
}
[UnityTearDown]
public IEnumerator UnityTearDown()
{
yield return new ExitPlayMode();
}
[UnityTest]
public IEnumerator Time_StartsRunning_InPlayMode()
{
float start = Time.time;
yield return null;
yield return null;
Assert.Greater(Time.time, start);
}
}
```
`EnterPlayMode` / `ExitPlayMode` are Edit Mode instructions. Yielding them from a Play Mode test is the wrong layer — you are already playing.
## 3.3 Async `Task` tests
From Test Framework 1.3.0 you can write:
```csharp
using System.Threading.Tasks;
using NUnit.Framework;
public class AsyncLoadTests
{
[Test]
public async Task FetchesAValue()
{
int value = await FakeService.GetValueAsync();
Assert.AreEqual(42, value);
}
}
static class FakeService
{
public static async Task<int> GetValueAsync()
{
await Task.Yield();
return 42;
}
}
```
This is the right shape when production code already returns `Task` or when you wrap a Unity `Awaitable` (see the Async Code tutorial later in this queue) with `.AsTask()` — **if** you add such a helper. Do **not** declare the test as `async Awaitable`. The supported async test return type is `Task` / `Task<T>`.
`[SetUp]` and `[TearDown]` can also be async `Task` methods. `[MaxTime]` applies to async tests and to `[UnityTest]` methods (1.4.5). Failing logs in an async test are evaluated after the async method completes (1.4.1).
## 3.4 Domain reloads
Edit Mode tests can force a script recompile and wait for the domain to come back:
```csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine.TestTools;
public class DomainReloadSmokeTests
{
[UnityTest]
public IEnumerator Recompile_DoesNotFailTheRunner()
{
yield return new RecompileScripts();
yield return new WaitForDomainReload();
Assert.IsTrue(true);
}
}
```
You rarely need this in class exercises. You need it when testing an editor tool that generates scripts or an `[InitializeOnLoad]` path. After a domain reload, **static fields and the test fixture instance are new** — stash anything you must keep in `SessionState` or a `ScriptableSingleton`, not in a field.
`Retry` and `Repeat` attributes work with `[UnityTest]`, including tests that reload the domain (called out as fixed in 1.1.20).
## 3.5 Timeouts
A test that never finishes will eventually trip a timeout. Default timeout behaviour is documented per method type; you can set `[Timeout(milliseconds)]` or `[MaxTime(milliseconds)]` (NUnit) on the method. Tests with an explicit timeout higher than 10 minutes no longer get cut off at 10 minutes when run from the command line (1.1.2), which matters for Player runs on a device.
If a Play Mode test hangs, the usual causes are:
- Waiting on an event that never fires
- `WaitForEndOfFrame` in a headless / batchmode player
- Forgetting to `yield` and instead spinning in a `while` on the main thread
Fail loud: after N frames, `Assert.Fail("health bar never appeared")` rather than waiting for the runner timeout.
## Official docs
- Test Framework 1.7 API: https://docs.unity3d.com/Packages/
[email protected]/api/UnityEngine.TestTools.html
- Changelog (async tests, UnityOneTimeSetUp, MaxTime, WaitForEndOfFrame batchmode): https://docs.unity3d.com/Packages/
[email protected]/changelog/CHANGELOG.html