SetValidator or a missed state transition in GameSession does not corrupt one mode; it corrupts all three simultaneously. This is why Domain and Application code must be tested to 100% coverage before any UI or network work builds on top of it.
The diagram below shows the testing strategy across layers, the tools used at each level, and the coverage targets. EditMode tests are fast and run on every push; PlayMode tests are reserved for integration concerns that genuinely require a running Unity scene.
Test Categories
EditMode tests run without a Unity process (no
MonoBehaviour lifecycle). They execute fast — a full Domain + Application suite should complete in under 5 seconds. PlayMode tests spin up a Unity scene and are slower; reserve them for integration concerns that genuinely require a running scene.
Writing Unit Tests
Basic SetValidator Test
This is the simplest form: no mocks needed becauseSetValidator is a pure function.
Testing GameSession with NSubstitute Mocks
When testingGameSession, inject mocked dependencies so your test controls exactly what ISetValidator and IAIScanner return.
Naming Convention
Every test method follows the three-part pattern:
Good names:
Validate_AllDifferent_ReturnsValidValidate_MixedColor_ReturnsInvalidWithReasonClaimSelected_InvalidSet_PointPenaltyAppliedAnySetExists_BoardWithNoValidSet_ReturnsFalseHandleCommand_InWaitingForServerState_DiscardsClaim
TestValidation— what is being validated? what result?Test1— meaninglessWorksCorrectly— every test should work correctly; this adds no information
AAA Pattern
Every test body must have three clearly separated sections. Use a blank line between each.Assert.IsTrue(validator.FindAllSets(board).Count > 0) — it obscures the intent and makes failures harder to diagnose.
Coverage Targets
Coverage percentage is a floor, not a goal. 100% branch coverage on
SetValidator with weak assertions is worthless. Write tests that would catch a real bug — wrong attribute check, off-by-one board expansion, missed penalty deduction.What to Test
SetValidator — Cover Every Combination
Board — Invariants
GameSession — State Machine
AI (deterministic with fixed seed)
What NOT to Test
- Auto-properties with no logic. Testing that
card.Color == Color.Redaftercard = new Card(Color.Red)proves nothing. - Visual appearance. Animation curves, particle counts, and shader parameters cannot be meaningfully asserted in code.
- Animation timing. If an animation takes 0.3 s, don’t write a test for “0.3 seconds elapsed.” That couples your test suite to an art decision.
- Unity internals. You don’t need to verify that
Text.text = "5"makes text appear on screen. Trust the engine.
Injecting Time for Deterministic Tests
GameSession timed mode relies on elapsed time. Never use Time.deltaTime directly in Domain or Application code — that is a Unity API. Instead, inject an ITimeProvider interface.
CI Integration
Every push to any branch and every PR triggers the full unit test suite. The pipeline fails — and the PR is blocked — if:- Any EditMode test fails
- Any PlayMode test fails
dotnet formatreports style violations- Any Roslyn analyzer rule fires at error severity
git push and watch the pipeline.
Common Mistakes
Related Pages
Coding Conventions
Naming and formatting rules that apply equally to test files.
Approved Patterns
Constructor injection makes mock substitution straightforward.
PR Checklist
The test-related gates every PR must pass before merge.
Phase Breakdown
Phase 2 and 3 DoDs that set the coverage baseline for the whole project.