t1k:unity:dots-testing:unit-testing
| Field | Value |
|---|---|
| Module | dots-testing |
| Version | 2.1.7 |
| Effort | high |
| Tools | — |
Keywords: DOTS, ECS testing, NUnit, unit testing
How to invoke
Section titled “How to invoke”/t1k:unity:dots-testing:unit-testingDOTS Unit Testing
Section titled “DOTS Unit Testing”Foundation:
/dots-ecs-core(ISystem, components, queries). Combat formulas: see shared constants in your core module.
Test Infrastructure
Section titled “Test Infrastructure”Shared Test Fixture (e.g., DOTSTestBase)
Section titled “Shared Test Fixture (e.g., DOTSTestBase)”File: Packages/[your-package]/Tests/EditMode/[YourTestBase].cs
Base class for all DOTS package tests. Creates isolated World with full system pipeline:
public abstract class DOTSTestBase{ protected World m_World; protected EntityManager m_Manager;
[SetUp] → new World("TestWorld"), registers ALL system groups + systems in order [TearDown] → World.Dispose()
protected void Update() → SimulationSystemGroup.Update() (runs full pipeline) protected Entity CreateStatsEntity(str, vit, intel) → entity with BaseStats + derived protected Entity CreateCombatEntity(hp) → stats + Health/Mana/DamageEvent/DeadTag/etc protected Entity CreateFullEntity(position, teamId) → combat + LocalTransform/MoveSpeed/Nav}Key: Update() runs the FULL system pipeline (Stats → Combat → AI → Nav → Skills → Inventory → Spawning). To isolate a single system, override SetUp() and register only the system under test.
Assembly Definition
Section titled “Assembly Definition”File: Packages/[your-package]/Tests/EditMode/[YourPackage].Tests.EditMode.asmdef
Required settings:
allowUnsafeCode: trueoverrideReferences: truewithnunit.framework.dlldefineConstraints: ["UNITY_INCLUDE_TESTS"]includePlatforms: ["Editor"]- References: ALL your package modules + Unity.Entities, Unity.Transforms, Unity.Mathematics, Unity.Collections, Unity.Burst, Unity.Jobs
Test Categories
Section titled “Test Categories”| Category | Fixture | Pattern |
|---|---|---|
| Pure utility (SpatialHashGrid) | None needed | Direct struct tests, Allocator.Temp |
| Component data validation | None needed | Assert default values, factory methods |
| Single system (isolated) | DOTSTestBase + override SetUp | Register one system, create minimal entities |
| Integration (multi-system) | DOTSTestBase | Use Update() for full pipeline |
| Performance | DOTSTestBase | Stopwatch around Update() with N entities |
Test File Organization (MANDATORY)
Section titled “Test File Organization (MANDATORY)”Rule: ALL test files MUST be placed in a subfolder matching their library module. Never leave test files at the root EditMode/ level (except DOTSTestBase.cs and cross-cutting integration/performance tests).
Packages/<package>/Tests/├── EditMode/│ ├── <Package>.Tests.EditMode.asmdef│ ├── <TestBase>.cs # Shared fixture (ONLY base class at root)│ ├── AI/ # AI module tests│ │ └── <SystemName>Tests.cs│ ├── Boss/ # Boss module tests│ ├── Combat/ # Combat module tests│ ├── Core/ # Core module tests (sprites, lifetime, etc.)│ ├── Inventory/ # Inventory module tests│ ├── Navigation/ # Navigation module tests (crowds, sideview)│ ├── Skills/ # Skills module tests (projectiles, AoE, etc.)│ ├── Spawning/ # Spawning module tests│ ├── Stats/ # Stats module tests│ ├── Structures/ # Structures module tests (tower, barracks)│ ├── SpawnIntegrationTests.cs # Cross-module integration (root OK)│ ├── CombatIntegrationTests.cs # Cross-module integration (root OK)│ └── PerformanceTests.cs # Scalability benchmarks (root OK)└── PlayMode/ └── <Package>.Tests.PlayMode.asmdefNaming: <SystemName>Tests.cs — matches the system under test. One file per system.
Folder: Must match library module path (e.g., Runtime/Combat/Systems/X.cs → Tests/EditMode/Combat/XTests.cs).
PlayMode vs EditMode Strategy
Section titled “PlayMode vs EditMode Strategy”Target ratio: 95% EditMode, 5% PlayMode.
EditMode DOTSTestBase creates a full ECS world and runs World.Update() synchronously — it tests ALL system logic including ordering, ECB flush, and multi-system integration. No rendering or frame timing required.
When to use PlayMode (only these 3 scenarios)
Section titled “When to use PlayMode (only these 3 scenarios)”- Rendering verification — entities visible on screen, frustum culling,
ChunkWorldRenderBoundsintegrity - Multi-frame time progression — BDP throttling over real frames, spawn timers depending on real
Time.DeltaTimeaccumulation - SubScene baking integration — verifying baked entities match authoring after full SubScene load
When NOT to use PlayMode
Section titled “When NOT to use PlayMode”- Individual system logic → EditMode
- Component data validation → EditMode
- System ordering → EditMode (
World.Update()respects[UpdateBefore/After]) - Entity queries and filters → EditMode
PlayMode costs
Section titled “PlayMode costs”- 10-20x slower than EditMode (scene load + frame waits)
- CI/CD friction: requires GPU or
xvfbvirtual framebuffer on Linux - Flaky: timing-sensitive, frame-dependent
- MCP alternative:
dots-validatoragent +validation_snapshottool does live Play mode verification without test framework overhead — prefer this for rendering/runtime checks
MANDATORY: Play Mode Validation After Implementation
Section titled “MANDATORY: Play Mode Validation After Implementation”- ALWAYS run Play mode validation (via MCP
manage_editorenter Play ordots-validator) after implementing new systems - EditMode tests verify logic only — they do NOT catch: SubScene baking failures, system ordering bugs in real SimulationSystemGroup, rendering issues, missing components from bakers, ECB playback timing
- Minimum check: Enter Play →
read_console→ zero errors → exit Play - Full check: Use
/gk:playtest(dots-validator agent) for entity spawn, movement, combat, rendering verification
Test Gotchas (discovered 2026-03-24)
Section titled “Test Gotchas (discovered 2026-03-24)”EnabledRefRW<T>inSystemAPI.Querygenerics implicitly filters to enabled-only — use.WithPresent<T>()to include disabled entitiesSystemAPI.GetSingletonEntity<T>()throws forIEnableableComponenttypes — useQuery<T>().WithEntityAccess()iteration insteadmanifest.jsonneeds"testables": ["com.the1studio.dots-puzzle"]for UPM package test discovery in Test Runner- ECB-spawned entities need TWO
Update()calls in tests: first creates via ECB, EndSimECB playback makes them exist for second update Assert.Ignorestubs give false coverage confidence — 3 test files indots-progressionuseAssert.Ignore("TODO")for every method; these show green in Test Runner but cover nothing. Any method body that is ONLYAssert.Ignore(...)must be filed as a tracked issue, not silently committed. Source: review-260520-round6-test-coverage.md §T3
Running Tests
Section titled “Running Tests”# EditMode (fast, no play mode)unity-editor -batchmode -nographics -projectPath "<path>" \ -runTests -testPlatform EditMode -testResults ./TestResults.xml
# In Editor: Window > General > Test Runner > EditMode tabQuick Reference
Section titled “Quick Reference”| Topic | Reference File |
|---|---|
| ISystem testing, ECB flush, EnableableComponent, DynamicBuffer, float comparison | test-patterns-guide.md |
| 24 common gotchas (incl. buffer-handle invalidation) + what NOT to test | gotchas-guide.md |