Claude-Code-Game-Studios test-helpers
Generate engine-specific test helper libraries for the project's test suite. Reads existing test patterns and produces tests/helpers/ with assertion utilities, factory functions, and mock objects tailored to the project's systems. Reduces boilerplate in new test files.
git clone https://github.com/Donchitos/Claude-Code-Game-Studios
T=$(mktemp -d) && git clone --depth=1 https://github.com/Donchitos/Claude-Code-Game-Studios "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/test-helpers" ~/.claude/skills/donchitos-claude-code-game-studios-test-helpers && rm -rf "$T"
.claude/skills/test-helpers/SKILL.mdTest Helpers
Writing test cases is faster and more consistent when common setup, teardown, and assertion patterns are abstracted into helpers. This skill generates a
tests/helpers/ library tailored to the project's actual engine, language,
and systems — so every developer writes less boilerplate and more assertions.
Output:
tests/helpers/ directory with engine-specific helper files
When to run:
- After
scaffolds the framework (first time)/test-setup - When multiple test files repeat the same setup boilerplate
- When starting to write tests for a new system
1. Parse Arguments
Modes:
— generate helpers for a specific system (e.g.,/test-helpers [system-name]
)/test-helpers combat
— generate helpers for all systems with test files/test-helpers all
— generate only the base helper library (no system-specific helpers); use this on first run/test-helpers scaffold- No argument — run
if no helpers exist, elsescaffoldall
2. Detect Engine and Language
Read
.claude/docs/technical-preferences.md and extract:
valueEngine:
valueLanguage:
from the Testing sectionFramework:
If engine is not configured: "Engine not configured. Run
/setup-engine first."
3. Load Existing Test Patterns
Scan the test directory for patterns already in use:
Glob pattern="tests/**/*_test.*" (all test files)
For a representative sample (up to 5 files), read the test files and extract:
- Setup patterns (how
/before_each
/ fixtures are written)setUp - Common assertion patterns (what is being asserted most often)
- Object creation patterns (how game objects or scenes are instantiated in tests)
- Mock/stub patterns (how dependencies are replaced)
This ensures generated helpers match the project's existing style, not a generic template.
Also read:
— to know which systems existdesign/gdd/systems-index.md- In-scope GDD(s) — to understand what data types and values need testing
— to map requirements to tested systemsdocs/architecture/tr-registry.yaml
4. Generate Engine-Specific Helpers
Godot 4 (GDUnit4 / GDScript)
Base helper (
tests/helpers/game_assertions.gd):
## Game-specific assertion utilities for [Project Name] tests. ## Extends GdUnitAssertions with domain-specific helpers. ## ## Usage: ## var assert = GameAssertions.new() ## assert.health_in_range(entity, 0, entity.max_health) class_name GameAssertions extends RefCounted ## Assert a value is within the inclusive range [min_val, max_val]. ## Use for any formula output that has defined bounds in a GDD. static func assert_in_range( value: float, min_val: float, max_val: float, label: String = "value" ) -> void: assert( value >= min_val and value <= max_val, "%s %.2f is outside expected range [%.2f, %.2f]" % [label, value, min_val, max_val] ) ## Assert a signal was emitted during a callable block. ## Usage: assert_signal_emitted(entity, "health_changed", func(): entity.take_damage(10)) static func assert_signal_emitted( obj: Object, signal_name: String, action: Callable ) -> void: var emitted := false obj.connect(signal_name, func(_args): emitted = true) action.call() assert(emitted, "Expected signal '%s' to be emitted, but it was not." % signal_name) ## Assert that a callable does NOT emit a signal. static func assert_signal_not_emitted( obj: Object, signal_name: String, action: Callable ) -> void: var emitted := false obj.connect(signal_name, func(_args): emitted = true) action.call() assert(not emitted, "Expected signal '%s' NOT to be emitted, but it was." % signal_name) ## Assert a node exists at path within a parent. static func assert_node_exists(parent: Node, path: NodePath) -> void: assert( parent.has_node(path), "Expected node at path '%s' to exist." % str(path) )
Factory helper (
tests/helpers/game_factory.gd):
## Factory functions for creating test game objects. ## Returns minimal objects configured for unit testing (no scene tree required). ## ## Usage: var player = GameFactory.make_player(health: 100) class_name GameFactory extends RefCounted ## Create a minimal player-like object for testing. ## Override fields as needed. static func make_player(health: int = 100) -> Node: var player = Node.new() player.set_meta("health", health) player.set_meta("max_health", health) return player
Scene helper (
tests/helpers/scene_runner_helper.gd):
## Utilities for scene-based integration tests. ## Wraps GdUnitSceneRunner for common patterns. class_name SceneRunnerHelper extends GdUnitTestSuite ## Load a scene and wait one frame for _ready() to complete. func load_scene_and_wait(scene_path: String) -> Node: var scene = load(scene_path).instantiate() add_child(scene) await get_tree().process_frame return scene
Unity (NUnit / C#)
Base helper (
tests/helpers/GameAssertions.cs):
using NUnit.Framework; using UnityEngine; /// <summary> /// Game-specific assertion utilities for [Project Name] tests. /// Extends NUnit's Assert with domain-specific helpers. /// </summary> public static class GameAssertions { /// <summary> /// Assert a value is within an inclusive range [min, max]. /// Use for any formula output defined in GDD Formulas sections. /// </summary> public static void AssertInRange(float value, float min, float max, string label = "value") { Assert.That(value, Is.InRange(min, max), $"{label} ({value:F2}) is outside expected range [{min:F2}, {max:F2}]"); } /// <summary>Assert a UnityEvent or C# event was raised during an action.</summary> public static void AssertEventRaised(ref bool wasCalled, System.Action action, string eventName) { wasCalled = false; action(); Assert.IsTrue(wasCalled, $"Expected event '{eventName}' to be raised, but it was not."); } /// <summary>Assert a component exists on a GameObject.</summary> public static void AssertHasComponent<T>(GameObject obj) where T : Component { var component = obj.GetComponent<T>(); Assert.IsNotNull(component, $"Expected GameObject '{obj.name}' to have component {typeof(T).Name}."); } }
Factory helper (
tests/helpers/GameFactory.cs):
using UnityEngine; /// <summary> /// Factory methods for creating minimal test objects without loading scenes. /// </summary> public static class GameFactory { /// <summary>Create a minimal GameObject with a named component for testing.</summary> public static GameObject MakeGameObject(string name = "TestObject") { var go = new GameObject(name); return go; } /// <summary> /// Create a ScriptableObject of type T for data-driven tests. /// Dispose with Object.DestroyImmediate after test. /// </summary> public static T MakeScriptableObject<T>() where T : ScriptableObject { return ScriptableObject.CreateInstance<T>(); } }
Unreal Engine (C++)
Base helper (
tests/helpers/GameTestHelpers.h):
#pragma once #include "CoreMinimal.h" #include "Misc/AutomationTest.h" /** * Game-specific assertion macros and helpers for [Project Name] automation tests. * Include in any test file that needs domain-specific assertions. * * Usage: * GAME_TEST_ASSERT_IN_RANGE(TestName, DamageValue, 10.0f, 50.0f, TEXT("Damage")); */ // Assert a float value is within inclusive range [Min, Max] #define GAME_TEST_ASSERT_IN_RANGE(TestName, Value, Min, Max, Label) \ TestTrue( \ FString::Printf(TEXT("%s (%.2f) in range [%.2f, %.2f]"), Label, Value, Min, Max), \ (Value) >= (Min) && (Value) <= (Max) \ ) // Assert a UObject pointer is valid (not null, not garbage collected) #define GAME_TEST_ASSERT_VALID(TestName, Ptr, Label) \ TestTrue( \ FString::Printf(TEXT("%s is valid"), Label), \ IsValid(Ptr) \ ) // Assert an Actor is in the world (spawned successfully) #define GAME_TEST_ASSERT_SPAWNED(TestName, ActorPtr, ClassName) \ TestNotNull( \ FString::Printf(TEXT("Spawned actor of class %s"), TEXT(#ClassName)), \ ActorPtr \ ) /** * Helper to create a minimal test world. * Remember to call World->DestroyWorld(false) in teardown. */ namespace GameTestHelpers { inline UWorld* CreateTestWorld(const FString& WorldName = TEXT("TestWorld")) { UWorld* World = UWorld::CreateWorld(EWorldType::Game, false); FWorldContext& WorldContext = GEngine->CreateNewWorldContext(EWorldType::Game); WorldContext.SetCurrentWorld(World); return World; } }
5. Generate System-Specific Helpers
For
[system-name] or all modes, generate a helper per system:
Read the system's GDD to extract:
- Data types (entity types, component names)
- Formula variables and their bounds
- Common test scenarios mentioned in Edge Cases
Generate
tests/helpers/[system]_factory.[ext] with factory functions
specific to that system's objects.
Example pattern for a
combat system (Godot/GDScript):
## Factory and assertion helpers for Combat system tests. ## Generated by /test-helpers combat on [date]. ## Based on: design/gdd/combat.md class_name CombatTestFactory extends RefCounted const DAMAGE_MIN := 0 const DAMAGE_MAX := 999 # From GDD: damage formula upper bound ## Create a minimal attacker object for damage formula tests. static func make_attacker(attack: float = 10.0, crit_chance: float = 0.0) -> Node: var attacker = Node.new() attacker.set_meta("attack", attack) attacker.set_meta("crit_chance", crit_chance) return attacker ## Create a minimal target object for damage receive tests. static func make_target(defense: float = 0.0, health: float = 100.0) -> Node: var target = Node.new() target.set_meta("defense", defense) target.set_meta("health", health) target.set_meta("max_health", health) return target ## Assert damage output is within GDD-specified bounds. static func assert_damage_in_bounds(damage: float) -> void: GameAssertions.assert_in_range(damage, DAMAGE_MIN, DAMAGE_MAX, "damage")
6. Write Output
Present a summary of what will be created:
## Test Helpers to Create Base helpers (engine: [engine]): - tests/helpers/game_assertions.[ext] - tests/helpers/game_factory.[ext] [engine-specific extras] System helpers ([mode]): - tests/helpers/[system]_factory.[ext] ← from [system] GDD
Ask: "May I write these helper files to
tests/helpers/?"
Never overwrite existing files. If a file already exists, report: "Skipping
[path] — already exists. Remove the file manually if you want it
regenerated."
After writing: Verdict: COMPLETE — helper files created.
"Helper files created. To use them in a test:
- Godot:
is auto-imported — no explicit import neededclass_name - Unity: Add
directive or reference the test assemblyusing - Unreal:
"#include \"tests/helpers/GameTestHelpers.h\"
Collaborative Protocol
- Never overwrite existing helpers — they may contain hand-written customisations. Only generate new files that don't exist yet
- Generated code is a starting point — the generated factory functions use metadata patterns for simplicity; adapt to the actual class structure once the code exists
- Helpers should reflect the GDD — bounds and constants in helpers should trace to GDD Formulas sections, not invented values
- Ask before writing — always confirm before creating files in
tests/
Next Steps
- Run
if the test framework has not been scaffolded yet./test-setup - Use
to implement stories — helpers reduce boilerplate in new test files./dev-story - Run
to validate other skills that may need helper coverage./skill-test