Let AI Write Your Unity Tests: Edit Mode and Play Mode Tests

Illustration: a column of glowing green checkmarks next to a code test panel

Most indie Unity projects have zero tests, and the usual reason is time. Writing tests is exactly the kind of repetitive, pattern-heavy work AI assistants do well. Used carelessly, though, AI produces tests that compile, pass, and prove nothing. This guide shows how to let AI write your Unity tests with the Unity Test Framework: what is worth testing, prompts that produce real NUnit Edit Mode and Play Mode tests, coroutine-based Play Mode tests, and how to catch the weak assertions AI loves to write.

Quick Setup Check

Tests live in their own assemblies. You need an Edit Mode test assembly, usually a Play Mode one, and your game code in its own assembly definition so the tests can reference it. Test assemblies can’t see code in Assembly-CSharp.

  • Open Window > General > Test Runner and use its buttons to create EditMode and PlayMode test assembly folders.
  • Put your game scripts under an .asmdef (for example Game.Runtime) and add it to each test assembly’s references.

If any of that is new, read our Unity Test Framework guide and the assembly definitions guide first. Tell the AI about this setup in every prompt. Otherwise it may write tests that reference types the test assembly can’t see.

What to Test (and What to Skip)

AI will happily write fifty tests for a getter. Point it at code where bugs actually hurt:

  • Game rules and maths: damage formulas, XP curves, cooldowns, score multipliers, loot rolls with a fixed seed.
  • Data structures: inventories, quest state, save data that is serialized and loaded back.
  • State machines: which transitions are allowed, what happens on invalid input.
  • Bugs you already fixed. One test per fixed bug stops it coming back.
  • Component behaviour that depends on Unity: physics, Awake and Start order, coroutines. These need Play Mode tests.

Skip trivial properties, Unity’s own behaviour (“does SetActive work”), and visuals that you’ll judge by eye anyway.

The easiest code to test is plain C# with no MonoBehaviour. If your logic is buried in Update(), ask the AI to extract it into a plain class first. That’s a small, safe refactor, and our guide to refactoring Unity code with AI shows how to do it without breaking Inspector data.

Prompting for Edit Mode Tests

Edit Mode tests run without entering Play mode, so they are fast. Use them for plain C# logic. A prompt that works:

Unity 6, Unity Test Framework, NUnit. Write Edit Mode tests for the
Inventory class below. The test assembly references Game.Runtime.

Requirements:
- One behaviour per test, named Method_Condition_ExpectedResult.
- Use concrete expected values, not values recomputed with the same
  formula as the code.
- Cover: adding to an empty slot, stacking up to maxStack, overflow
  when full, removing more than you have, invalid item ids.
- If the current behaviour looks like a bug, write the test for the
  CORRECT behaviour, mark it [Ignore("Bug: ...")] and tell me.

[paste Inventory.cs]

Typical output for an inventory with a capacity and stack limit:

using NUnit.Framework;
using Game;

public class InventoryTests
{
    private Inventory inventory;

    [SetUp]
    public void SetUp()
    {
        inventory = new Inventory(slotCount: 2, maxStack: 10);
    }

    [Test]
    public void Add_ToEmptyInventory_ReturnsZeroLeftover()
    {
        int leftover = inventory.Add("potion", 3);

        Assert.AreEqual(0, leftover);
        Assert.AreEqual(3, inventory.Count("potion"));
    }

    [Test]
    public void Add_MoreThanCapacity_ReturnsLeftover()
    {
        int leftover = inventory.Add("potion", 25);

        Assert.AreEqual(5, leftover);            // 2 slots x 10
        Assert.AreEqual(20, inventory.Count("potion"));
    }

    [TestCase(0)]
    [TestCase(-1)]
    public void Add_NonPositiveAmount_Throws(int amount)
    {
        Assert.Throws<System.ArgumentOutOfRangeException>(
            () => inventory.Add("potion", amount));
    }

    [Test]
    public void Remove_MoreThanHeld_RemovesNothingAndReturnsFalse()
    {
        inventory.Add("potion", 3);

        bool removed = inventory.Remove("potion", 5);

        Assert.IsFalse(removed);
        Assert.AreEqual(3, inventory.Count("potion"));
    }
}

Notice the expected values are plain numbers like 5 and 20, not slotCount * maxStack - 25. That matters, and we’ll come back to it. Also check the AI didn’t invent behaviour: if your Add doesn’t throw on zero, the test above documents a decision you haven’t made yet. Decide, then keep or change the test.

Play Mode Tests With Coroutines

Anything that needs frames to pass, physics, or MonoBehaviour lifecycle methods belongs in a Play Mode test. These use [UnityTest] and return IEnumerator, so you can yield between frames.

using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Game;

public class HealthPlayModeTests
{
    private GameObject player;

    [TearDown]
    public void TearDown()
    {
        if (player != null) Object.Destroy(player);
    }

    [UnityTest]
    public IEnumerator TakeDamage_PastZero_RaisesDiedOnce()
    {
        player = new GameObject("Player");
        var health = player.AddComponent<Health>();   // Awake runs here in Play Mode
        int diedCount = 0;
        health.Died += () => diedCount++;

        health.TakeDamage(500);
        health.TakeDamage(500);
        yield return null;

        Assert.AreEqual(0, health.Current);
        Assert.AreEqual(1, diedCount);
    }

    [UnityTest]
    public IEnumerator Rigidbody_WithGravity_FallsOverTime()
    {
        player = GameObject.CreatePrimitive(PrimitiveType.Cube);
        var body = player.AddComponent<Rigidbody>();
        float startY = body.position.y;

        for (int i = 0; i < 10; i++)
            yield return new WaitForFixedUpdate();

        Assert.Less(body.position.y, startY);
    }
}

Things AI commonly gets wrong here:

  • Calling MonoBehaviour logic in Edit Mode tests. In Edit Mode, AddComponent doesn’t run Awake or Start on normal scripts, so fields stay uninitialized. The test fails or, worse, passes for the wrong reason.
  • Waiting with real time. yield return new WaitForSeconds(3) makes the suite slow. Prefer a fixed number of frames or physics steps, or make durations configurable so tests can use short ones.
  • Not cleaning up. Objects created in one test leak into the next. Use [TearDown], or [UnityTearDown] if cleanup needs to yield.
  • Loading full scenes for everything. Building only the objects a test needs is faster and less fragile than loading your main level.

📚 Get better at judging tests

AI can generate tests quickly, but knowing which tests are worth having is a skill you build by reading how experienced programmers think about design and testing. We picked the books that help most.

See our programming and game design book picks →

Spotting Weak and Fake Tests

This is the part that matters most. AI is under pressure to make tests pass, and some passing tests are useless. Watch for:

  • Only checking for null. Assert.IsNotNull(result) as the only assertion proves the method returned something, not that it was right.
  • Recomputing the expected value with the same formula. Assert.AreEqual(baseDmg * 1.5f - armor, Damage.Calc(...)) copies the code under test. If the formula is wrong, the test is wrong in the same way.
  • Testing the mock, not the code. Heavily stubbed tests can end up only checking that the stub returns what you told it to.
  • Assertions inside branches that never run, for example an if or a loop over an empty list.
  • Swallowed exceptions. A try/catch around the call with no assertion in the catch.
  • Changing the code to make tests pass. If an agent edits your game code while “writing tests”, review that change separately. It might be a real bug fix or it might be hiding one.
  • Float comparisons without tolerance. Use Assert.That(value, Is.EqualTo(1.5f).Within(0.0001f)) for floats.

A quick follow-up prompt helps: “For each test, explain what bug it would catch. Remove any test that wouldn’t catch a realistic bug.”

Break the Code to Test the Tests

The fastest way to know whether a test is real: break the code on purpose. Change a > to >=, remove a clamp, return early. Run the tests. If nothing fails, the tests don’t cover that line.

You can make the AI do this too:

List five small, realistic bugs someone could introduce into
Inventory.Add (off-by-one, missing clamp, wrong stack math).
For each one, tell me which of the current tests would fail.
If none would, write a test that does.

Then actually try a couple of those mutations yourself and revert them with git. It takes five minutes and tells you far more than a coverage number.

Running Tests From the Command Line

Agentic tools that work in a terminal, like Claude Code, can run your tests and read the results, which closes the loop: write a test, run it, fix, repeat. Unity can run tests in batch mode:

Unity.exe -batchmode -projectPath "C:\Projects\MyGame" -runTests -testPlatform EditMode -testResults "C:\Projects\MyGame\TestResults\editmode.xml" -logFile -

Use -testPlatform PlayMode for Play Mode tests. Don’t add -quit, because the test run exits on its own. The editor must not already have the project open, so close it first or run tests through an Editor connection instead. If your assistant connects through MCP, some Unity MCP servers can trigger test runs in the open editor; see our Unity MCP setup guide.

Keep a human in the loop: an agent that runs tests until they pass can “fix” a failure by weakening the test. Review test diffs as carefully as code diffs.

Troubleshooting

“The type or namespace name ‘NUnit’ could not be found”

The test file isn’t inside a test assembly. Move it into the folder with the test .asmdef, or check that the .asmdef references nunit.framework.dll and has UNITY_INCLUDE_TESTS in Define Constraints.

CS0246 for your own game types in a test

The test assembly doesn’t reference your game assembly, or your game code is still in Assembly-CSharp. Add an .asmdef to your scripts folder and reference it from the tests.

“Unhandled log message: ‘[Error] …’. Use UnityEngine.TestTools.LogAssert.Expect”

Your code logged an error during the test, which fails it by default. If the error is expected, add LogAssert.Expect(LogType.Error, "exact message"); before the call. If it isn’t, you found a real problem.

Fields are zero or null in an Edit Mode test of a MonoBehaviour

Awake and Start didn’t run. Move the test to Play Mode, or move the logic into a plain C# class you can construct directly.

Tests don’t appear in the Test Runner

Check you’re on the right tab (EditMode vs PlayMode), the class and methods are public, and there are no compile errors. A single error anywhere stops the list from refreshing.

Play Mode tests pass alone but fail together

Leftover objects or static state from earlier tests. Destroy what you create in TearDown and reset static fields in SetUp.

FAQ

Can AI write good Unity tests?

Yes, for clear, plain C# logic it is very effective. It needs firm rules about concrete expected values and one behaviour per test, and you still need to check the assertions are meaningful.

Edit Mode or Play Mode tests?

Edit Mode for anything that is plain C#, because they run fast. Play Mode for physics, coroutines, MonoBehaviour lifecycle and scene setups.

Should AI write tests before or after the code?

Either works. Writing tests from a description of the desired behaviour before the code makes it less likely the tests just copy the implementation.

How many tests does an indie game need?

Far fewer than you think. A few dozen tests around core rules, save data and past bugs give most of the benefit.

Can AI test my UI?

It can write Play Mode tests that call button handlers or check UI state, but layout and feel are still best checked by eye.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *