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.
What you will build
Section titled “What you will build”A dedicated test area for a subsystem (for example, Assets/_Game/Tests/EditMode/Weather/ for weather simulation):
- An isolated Assembly Definition (
VoxelSandbox.Weather.Tests.asmdef) quarantined to theEditorplatform. - Direct references restricted to
VoxelSandbox.World,VoxelSandbox.Core, and Unity’sTestAssemblies. - An NUnit test fixture (
WeatherCycleTests.cs) locking down state transitions and boundary edge cases. - CLI test automation integration via batchmode command lines.
Where this sits
Section titled “Where this sits”Test assemblies in Voxamine follow three strict architectural rules:
- 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.
- Editor platform quarantine: Test assemblies have
"includePlatforms": ["Editor"]and"autoReferenced": false. They are never compiled into standalone player builds. - 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;
Before you start
Section titled “Before you start”Reading manifest
Section titled “Reading manifest”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. |
Intake decisions
Section titled “Intake decisions”| 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. |
The files, in order
Section titled “The files, in order”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.
Step 2: Author the test fixture
Section titled “Step 2: Author the test fixture”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.
Verify
Section titled “Verify”1. In the Unity Editor GUI
Section titled “1. In the Unity Editor GUI”- Open the Test Runner window: Window ▸ General ▸ Test Runner.
- Switch to the EditMode tab.
- Locate
VoxelSandbox.Weather.Testsin the assembly list. - 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:
/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.logInspect Logs/editmode-weather-results.xml to verify:
- Total test count matches expectations.
- Zero failed tests (
failures="0"). - Execution finishes in under a second.
Now do your own
Section titled “Now do your own”Use this checklist whenever adding a new test assembly or test area:
- Name and place: Create
Assets/_Game/Tests/EditMode/<Area>/. Name the assemblyVoxelSandbox.<Area>.Tests.asmdef. - Isolate references: Add only the target runtime assembly and necessary dependencies to
"references". - Include TestAssemblies: Always add
"optionalUnityReferences": ["TestAssemblies"]. - Set Editor platform: Always restrict
"includePlatforms": ["Editor"]. - Keep tests pure: Prefer pure unit tests over integration tests requiring heavy GameObject hierarchies.
- Clean up disk writes: If writing temporary files, always wrap in a unique directory and delete it in
[TearDown]. - Tag categories: Use
[Category("<Area>")]so test suites can be filtered in CI.
Pitfalls
Section titled “Pitfalls”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
.asmdeflacks"optionalUnityReferences": ["TestAssemblies"]. - Fix: Add
"optionalUnityReferences": ["TestAssemblies"]to the.asmdefJSON.
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
.asmdefomitted"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.assetshows unstaged modifications. - Cause: Unity Editor writes editor session settings during
-runTests. - Fix: Run
git checkout ProjectSettings/ProjectSettings.assetafter 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.