| name | unity-testing |
| description | This skill should be used when the user asks about "tests", "unit tests", "EditMode tests", "PlayMode tests", "Test Runner", "test coverage", "NUnit", "Assert", "test fixtures", "writing tests", "running tests", or discusses Unity testing patterns and practices. |
| version | 0.2.0 |
Unity Testing
Expert knowledge of Unity testing patterns, EditMode/PlayMode tests, and test-driven development for the Zero-Day Attack project.
Test Types
EditMode Tests
Run without entering Play mode - fast iteration:
| Property | Value |
|---|---|
| Location | Assets/Tests/Editor/ |
| Assembly | *.Tests.Editor.asmdef |
| Speed | Fast (no scene loading) |
| Use For | Pure logic, data validation, calculations |
PlayMode Tests
Run in simulated Play mode - test runtime behavior:
| Property | Value |
|---|---|
| Location | Assets/Tests/Runtime/ |
| Assembly | *.Tests.Runtime.asmdef |
| Speed | Slower (scene setup required) |
| Use For | MonoBehaviour, scene interactions, integration |
Project Test Structure
Assets/Tests/
├── Editor/
│ ├── BoardLayoutConfigTests.cs # LayoutConfig validation
│ └── TileDatabaseTests.cs # Database integrity
└── Runtime/
├── CoordinateConversionTests.cs # Grid-to-world conversion
└── GameInitializationTests.cs # Startup and state
Writing EditMode Tests
EditMode Test Basic Structure
using NUnit.Framework;
using ZeroDayAttack.Config;
namespace ZeroDayAttack.Tests.Editor
{
[TestFixture]
public class LayoutConfigTests
{
[Test]
public void GridSize_ShouldBeFive()
{
Assert.AreEqual(5, LayoutConfig.GridSize);
}
[Test]
public void ScreenDimensions_ShouldMatch1920x1080()
{
// At 100 PPU: 1920/100 = 19.2, 1080/100 = 10.8
Assert.AreEqual(19.2f, LayoutConfig.ScreenWidth, 0.001f);
Assert.AreEqual(10.8f, LayoutConfig.ScreenHeight, 0.001f);
}
[Test]
public void CameraOrthoSize_ShouldBeHalfHeight()
{
float expectedOrtho = LayoutConfig.ScreenHeight / 2f;
Assert.AreEqual(expectedOrtho, LayoutConfig.CameraOrthoSize, 0.001f);
}
}
}
Testing ScriptableObjects
using NUnit.Framework;
using UnityEngine;
using ZeroDayAttack.Core.Data;
namespace ZeroDayAttack.Tests.Editor
{
[TestFixture]
public class TileDatabaseTests
{
private TileDatabase database;
[OneTimeSetUp]
public void LoadDatabase()
{
database = Resources.Load<TileDatabase>("TileDatabase");
}
[Test]
public void Database_ShouldLoad()
{
Assert.IsNotNull(database, "TileDatabase not found in Resources");
}
[Test]
public void Database_ShouldHave25Tiles()
{
Assert.AreEqual(25, database.Tiles.Count);
}
[Test]
public void AllTiles_ShouldHaveSprites()
{
foreach (var tile in database.Tiles)
{
Assert.IsNotNull(tile.TileSprite,
$"Tile {tile.TileId} missing sprite");
}
}
}
}
Writing PlayMode Tests
PlayMode Test Basic Structure
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using ZeroDayAttack.View;
namespace ZeroDayAttack.Tests.Runtime
{
[TestFixture]
public class TileManagerTests
{
private TileManager tileManager;
[UnitySetUp]
public IEnumerator SetUp()
{
// Create test object
var go = new GameObject("TileManager");
tileManager = go.AddComponent<TileManager>();
yield return null; // Wait one frame
}
[UnityTearDown]
public IEnumerator TearDown()
{
if (tileManager != null)
{
Object.Destroy(tileManager.gameObject);
}
yield return null;
}
[UnityTest]
public IEnumerator Instance_ShouldBeSet()
{
yield return null;
Assert.IsNotNull(TileManager.Instance);
}
[UnityTest]
public IEnumerator GridToWorld_CenterTile_ShouldBeAtOrigin()
{
yield return null;
var worldPos = tileManager.GetWorldPosition(2, 2);
Assert.AreEqual(0f, worldPos.x, 0.001f);
Assert.AreEqual(0f, worldPos.y, 0.001f);
}
}
}
Testing with Scenes
using UnityEngine.SceneManagement;
[UnityTest]
public IEnumerator GameplayScene_ShouldLoad()
{
SceneManager.LoadScene("GameplayScene");
yield return null; // Wait for load
var gameManager = GameObject.FindObjectOfType<GameManager>();
Assert.IsNotNull(gameManager);
}
Common Assertions
Value Assertions
Assert.AreEqual(expected, actual);
Assert.AreEqual(expected, actual, delta); // For floats
Assert.AreNotEqual(notExpected, actual);
Assert.IsTrue(condition);
Assert.IsFalse(condition);
Null Assertions
Assert.IsNull(obj);
Assert.IsNotNull(obj);
Collection Assertions
Assert.Contains(item, collection);
Assert.IsEmpty(collection);
Assert.IsNotEmpty(collection);
CollectionAssert.AreEqual(expected, actual);
CollectionAssert.AreEquivalent(expected, actual); // Order-independent
Exception Assertions
Assert.Throws<ArgumentException>(() => {
SomeMethod(invalidArg);
});
Assert.DoesNotThrow(() => {
SomeMethod(validArg);
});
Running Tests via MCP
Run All EditMode Tests
{
"testMode": "EditMode"
}
Run Specific Test Class
{
"testMode": "EditMode",
"testClass": "LayoutConfigTests"
}
Run Specific Test Method
{
"testMode": "EditMode",
"testMethod": "ZeroDayAttack.Tests.Editor.LayoutConfigTests.GridSize_ShouldBeFive"
}
Useful Options
{
"testMode": "EditMode",
"includePassingTests": false, // Only show failures
"includeMessages": true, // Include assertion messages
"includeStacktrace": true, // Include stack traces
"includeLogs": true // Include console logs
}
Test Naming Conventions
Pattern: Method_Scenario_ExpectedResult
[Test] public void GridToWorld_CenterTile_ReturnsOrigin() { }
[Test] public void PlaceTile_InvalidPosition_ThrowsException() { }
[Test] public void MoveToken_ValidPath_UpdatesPosition() { }
Test Class Naming
public class GameManagerTests { } // Tests for GameManager
public class TileValidationTests { } // Tests for tile validation logic
public class PathFindingTests { } // Tests for path algorithms
Test Categories
Organize tests with categories:
[Test]
[Category("FastTests")]
public void QuickCalculation_Test() { }
[Test]
[Category("Integration")]
public void SceneSetup_Test() { }
Run by category:
{
"testMode": "EditMode",
"testCategory": "FastTests"
}
Testing Best Practices
Arrange-Act-Assert Pattern
[Test]
public void PathSegment_RotateBy90_UpdatesNodes()
{
// Arrange
var segment = new PathSegment(EdgeNode.Left, EdgeNode.Top, PathColor.Blue);
// Act
var rotated = segment.Rotate(90);
// Assert
Assert.AreEqual(EdgeNode.Top, rotated.From);
Assert.AreEqual(EdgeNode.Right, rotated.To);
}
One Assertion Per Test (When Practical)
// Good: Focused tests
[Test] public void Tile_HasCorrectWidth() { }
[Test] public void Tile_HasCorrectHeight() { }
// OK for related properties
[Test]
public void Tile_HasCorrectDimensions()
{
Assert.AreEqual(2.0f, tile.Width);
Assert.AreEqual(2.0f, tile.Height);
}
Test Independence
Each test should be independent:
- Use
[SetUp]for common initialization - Use
[TearDown]for cleanup - Don't rely on test execution order
Fast Tests
Prefer EditMode tests when possible:
- No scene loading overhead
- Pure logic tests run instantly
- Use PlayMode only when necessary
Existing Tests
BoardLayoutConfigTests
Tests LayoutConfig constants:
- Grid dimensions
- Screen dimensions
- Camera settings
- Zone coordinates
TileDatabaseTests
Tests TileDatabase integrity:
- Database loads from Resources
- Correct tile count (25)
- All tiles have sprites
- All tiles have valid path data
CoordinateConversionTests
Tests TileManager coordinate conversion:
- Grid-to-world mapping
- Center tile at origin
- Corner positions correct
GameInitializationTests
Tests game startup:
- Managers initialize correctly
- Scene hierarchy valid
- Initial state correct
Additional Resources
Reference Files
For test examples:
- Assets/Tests/Editor/ - EditMode test examples
- Assets/Tests/Runtime/ - PlayMode test examples
Running Tests in Unity
Window > General > Test Runner
- EditMode tab for editor tests
- PlayMode tab for runtime tests