Skip to content
Edit on GitHub

Add an EditMode test area

verifiedAgainst f1eaefe · verifiedOn 2026-09-10 · the automated staleness banner is planned, not built.

This guide walks through setting up a brand-new EditMode test assembly in Assets/_Game/Tests/EditMode/, configuring strict assembly boundaries, authoring deterministic NUnit test fixtures, and running them both in the Editor GUI and via headless CLI.

A dedicated test area for a subsystem (for example, Assets/_Game/Tests/EditMode/Weather/ for weather simulation):

  1. An isolated Assembly Definition (VoxelSandbox.Weather.Tests.asmdef) quarantined to the Editor platform.
  2. Direct references restricted to VoxelSandbox.World, VoxelSandbox.Core, and Unity’s TestAssemblies.
  3. An NUnit test fixture (WeatherCycleTests.cs) locking down state transitions and boundary edge cases.
  4. CLI test automation integration via batchmode command lines.

Test assemblies in Voxamine follow three strict architectural rules:

  1. Strict one-way dependency: A test assembly references the subsystem under test and its upstream dependencies, but no runtime assembly ever references a test assembly.
  2. Editor platform quarantine: Test assemblies have "includePlatforms": ["Editor"] and "autoReferenced": false. They are never compiled into standalone player builds.
  3. Pure isolation: Tests execute in EditMode without entering Play Mode (-testPlatform EditMode), ensuring tests run in milliseconds without scene loading overhead.
flowchart TD
  subgraph ProductionAssemblies ["Runtime Assemblies"]
    CORE["VoxelSandbox.Core\nMath & Primitives"]
    DATA["VoxelSandbox.Data\nBlock & Substance Catalogs"]
    WORLD["VoxelSandbox.World\nTerrain & Weather Simulation"]
  end

  subgraph TestAssembliesBoundary ["Test Quarantine (Editor Only)"]
    NUNIT["TestAssemblies\n(Unity NUnit & Test Runner)"]
    WEATHER_TESTS["VoxelSandbox.Weather.Tests.asmdef\n(Your new test assembly)"]
    FIXTURE["WeatherCycleTests.cs\nNUnit [TestFixture]"]
  end

  WEATHER_TESTS --> NUNIT
  WEATHER_TESTS --> WORLD
  WEATHER_TESTS --> CORE
  WEATHER_TESTS -. optional .-> DATA
  FIXTURE --> WEATHER_TESTS

  classDef prod fill:#0c4f48,stroke:#6fd8c6,stroke-width:1px,color:#f8f9fa;
  classDef test fill:#1e1e24,stroke:#e58a63,stroke-width:1px,color:#f8f9fa;
  class CORE,DATA,WORLD prod;
  class NUNIT,WEATHER_TESTS,FIXTURE test;

Read these files at pinned commit f1eaefe before authoring:

# File Symbol / What to extract
1 Assets/_Game/Tests/EditMode/Inventory/VoxelSandbox.Inventory.Tests.asmdef Minimal test assembly definition: references, includePlatforms, optionalUnityReferences.
2 Assets/_Game/Tests/EditMode/Inventory/HotbarTests.cs Structure of a zero-allocation, pure-logic NUnit fixture.
3 Assets/_Game/Tests/EditMode/World/SaveGameServiceTests.cs Temporary directory setup in [SetUp] and cleanup in [TearDown].
4 development/build-and-test.md Official command-line arguments for running EditMode tests in batch mode.
Decision Choice Why
Test platform EditMode Runs fast, runs headless, tests pure domain logic without camera or rendering stack.
Namespace VoxelSandbox.Tests Consistent test root namespace across all game modules.
Assembly name VoxelSandbox.<Subsystem>.Tests Clear 1:1 mirroring of the runtime subsystem assembly name.

Step 1: Create the test directory and Assembly Definition

Section titled “Step 1: Create the test directory and Assembly Definition”

Create the directory Assets/_Game/Tests/EditMode/Weather/. Inside it, create VoxelSandbox.Weather.Tests.asmdef:

{
"name": "VoxelSandbox.Weather.Tests",
"rootNamespace": "VoxelSandbox.Tests",
"references": [
"VoxelSandbox.Core",
"VoxelSandbox.World"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false,
"optionalUnityReferences": [
"TestAssemblies"
]
}

Key fields:

  • "references": Include only the assembly under test (VoxelSandbox.World) and any types it exposes that your tests need (VoxelSandbox.Core).
  • "includePlatforms": ["Editor"]: Ensures Unity excludes these files during standalone builds.
  • "autoReferenced": false: Prevents other assemblies from accidentally binding to test code.
  • "optionalUnityReferences": ["TestAssemblies"]: Injects NUnit framework and Unity test runner primitives.

Create Assets/_Game/Tests/EditMode/Weather/WeatherCycleTests.cs:

using NUnit.Framework;
using VoxelSandbox.World.Weather;
namespace VoxelSandbox.Tests
{
/// <summary>
/// Validates weather transition logic and moisture decay curves.
/// </summary>
[TestFixture]
[Category("World")]
public sealed class WeatherCycleTests
{
private WeatherTimeline timeline;
[SetUp]
public void SetUp()
{
timeline = new WeatherTimeline(seed: 1337);
}
[TearDown]
public void TearDown()
{
timeline = null;
}
[Test]
public void Evaluate_AtZeroElapsedSeconds_ReturnsClearWeather()
{
WeatherState state = timeline.Evaluate(elapsedSeconds: 0f);
Assert.That(state.Condition, Is.EqualTo(WeatherCondition.Clear));
Assert.That(state.RainIntensity, Is.EqualTo(0f));
}
[TestCase(100f, WeatherCondition.Overcast)]
[TestCase(300f, WeatherCondition.Rain)]
[TestCase(600f, WeatherCondition.Clear)]
public void Evaluate_OverTimelineProgression_MatchesDeterministicForecast(
float elapsedSeconds,
WeatherCondition expectedCondition)
{
WeatherState state = timeline.Evaluate(elapsedSeconds);
Assert.That(state.Condition, Is.EqualTo(expectedCondition));
}
[Test]
public void SoilMoisture_DuringHeavyRain_IncreasesMonotonically()
{
float initialMoisture = 0.2f;
float updatedMoisture = timeline.ApplyMoisture(initialMoisture, rainIntensity: 1.0f, deltaTime: 10f);
Assert.That(updatedMoisture, Is.GreaterThan(initialMoisture));
Assert.That(updatedMoisture, Is.LessThanOrEqualTo(1.0f));
}
}
}

Step 3: Handling filesystem or temporary assets

Section titled “Step 3: Handling filesystem or temporary assets”

If your tests create files or assets, always isolate them within a temporary directory created in [SetUp] and deleted in [TearDown]:

private string tempPath;
[SetUp]
public void SetUp()
{
tempPath = Path.Combine(Path.GetTempPath(), "VoxelSandboxTests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempPath);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(tempPath))
{
Directory.Delete(tempPath, true);
}
}

Never write test output directly to Assets/ or ProjectSettings/ — doing so dirties the git tree and breaks automated CI pipelines.


  1. Open the Test Runner window: Window ▸ General ▸ Test Runner.
  2. Switch to the EditMode tab.
  3. Locate VoxelSandbox.Weather.Tests in the assembly list.
  4. Click Run All and verify all tests show green checkmarks.

2. Via the Command Line (Headless Batchmode)

Section titled “2. Via the Command Line (Headless Batchmode)”

Run the test suite using Unity batch mode:

Terminal window
/home/soulwax/Unity/Hub/Editor/6000.6.0f1/Editor/Unity \
-batchmode -nographics \
-projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \
-runTests \
-testPlatform EditMode \
-testCategory World \
-testResults Logs/editmode-weather-results.xml \
-logFile Logs/test-weather.log

Inspect Logs/editmode-weather-results.xml to verify:

  • Total test count matches expectations.
  • Zero failed tests (failures="0").
  • Execution finishes in under a second.

Use this checklist whenever adding a new test assembly or test area:

  1. Name and place: Create Assets/_Game/Tests/EditMode/<Area>/. Name the assembly VoxelSandbox.<Area>.Tests.asmdef.
  2. Isolate references: Add only the target runtime assembly and necessary dependencies to "references".
  3. Include TestAssemblies: Always add "optionalUnityReferences": ["TestAssemblies"].
  4. Set Editor platform: Always restrict "includePlatforms": ["Editor"].
  5. Keep tests pure: Prefer pure unit tests over integration tests requiring heavy GameObject hierarchies.
  6. Clean up disk writes: If writing temporary files, always wrap in a unique directory and delete it in [TearDown].
  7. Tag categories: Use [Category("<Area>")] so test suites can be filtered in CI.

Pitfall 1: Missing TestAssemblies reference

Section titled “Pitfall 1: Missing TestAssemblies reference”
  • Symptom: Compilation error: The type or namespace name 'NUnit' could not be found.
  • Cause: The .asmdef lacks "optionalUnityReferences": ["TestAssemblies"].
  • Fix: Add "optionalUnityReferences": ["TestAssemblies"] to the .asmdef JSON.

Pitfall 2: Test code compiled into release player

Section titled “Pitfall 2: Test code compiled into release player”
  • Symptom: Release build fails with compilation errors about missing test types, or game executable contains test classes.
  • Cause: The .asmdef omitted "includePlatforms": ["Editor"].
  • Fix: Ensure "includePlatforms" specifies only "Editor".

Pitfall 3: Git working tree dirtied by ProjectSettings.asset

Section titled “Pitfall 3: Git working tree dirtied by ProjectSettings.asset”
  • Symptom: After running tests via CLI, ProjectSettings/ProjectSettings.asset shows unstaged modifications.
  • Cause: Unity Editor writes editor session settings during -runTests.
  • Fix: Run git checkout ProjectSettings/ProjectSettings.asset after local test runs.

Pitfall 4: Flaky tests from static state leakage

Section titled “Pitfall 4: Flaky tests from static state leakage”
  • Symptom: Tests pass when run individually, but fail intermittently when run as part of the full test suite.
  • Cause: A test mutated static state (e.g. singletons, static caches) without resetting it in [TearDown].
  • Fix: Reset all modified static fields and caches in [TearDown].

User-contributed notes

Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works: **bold**, *italic*, `code`, and links.

Notes policy

Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked nofollow.

  1. Loading notes…