Phaser unit testing patterns with MockScene. Provides patterns for testing Phaser objects, Matter.js physics, and game systems without requiring a running game instance.
Phaser unit testing patterns with MockScene. Provides patterns for testing Phaser objects, Matter.js physics, and game systems without requiring a running game instance.
category
testing
Phaser Unit Testing Patterns
"Test Phaser game objects without a running game instance."
When to Use This Skill
Use when creating unit tests for:
Phaser game objects (Birds, Pigs, Blocks, etc.)
Matter.js physics bodies
Game systems that depend on Phaser Scene
Event-driven game logic
CRITICAL LESSON FROM BUG-011: Never use real Phaser Scene instances in unit tests. They require a running Phaser Game instance and will fail with errors like "scene.init is not a function". Always use MockScene pattern.
The MockScene Pattern
Why MockScene is Required
Real Phaser Scenes need:
A running Phaser.Game instance
Canvas element in DOM
Full Phaser engine initialization
Matter.js physics engine context
This makes unit tests:
Slow (full engine startup)
Brittle (DOM dependencies)
Complex (setup/teardown)
Unreliable (timing issues)
MockScene Pattern Solution:
Create comprehensive mocks that simulate Phaser Scene APIs without requiring the full engine.
Always use MockScene - Never instantiate real Phaser.Scene in unit tests
Mock all scene properties used - If code uses scene.time.addEvent, mock it
Test behavior, not Phaser internals - Test game logic, not framework calls
Use beforeEach for clean state - Create fresh MockScene for each test
Clean up event listeners - Remove listeners in test cleanup
Test edge cases - Zero health, max health, boundary conditions
Verify event emissions - Ensure game events fire correctly
Anti-Patterns
❌ DON'T:
// WRONG - Using real Sceneimport { Game, Scene } from'phaser';
test('should create pig', () => {
const game = newGame(config); // Slow and brittleconst scene = newScene();
game.scene.add('test', scene);
// ... tests will fail if canvas not in DOM
});
✅ DO:
// RIGHT - Using MockSceneimport { MockScene } from'../helpers/MockScene';
test('should create pig', () => {
const scene = newMockScene(); // Fast and reliableconst pig = newSmallPig(scene, 100, 200);
expect(pig.health).toBe(10);
});
qa-unit-test-creation skill - General unit testing patterns
LESSON LEARNED FROM BUG-011: The MockScene pattern is essential for testing Phaser objects. Always create comprehensive mocks for all Phaser Scene properties used by your code. This prevents test failures caused by missing Phaser Game instances.
UI Component Testing Patterns (feat-027)
Lesson from feat-027 (Star Rating Preview): UI components with animations require specific testing patterns.
Testing UI Update Methods
// tests/unit/ui/HUD.test.tsimport { describe, test, expect, beforeEach, vi } from'vitest';
import { MockScene } from'../helpers/MockScene';
import { HUD } from'@/ui/HUD';
describe('HUD Star Rating Preview', () => {
letscene: MockScene;
lethud: HUD;
beforeEach(() => {
scene = newMockScene();
hud = newHUD(scene);
});
describe('star threshold calculation', () => {
test('should calculate 2-star threshold correctly per level', () => {
// Level 1: 32,000 + (1 × 2,000) = 34,000expect(hud.getTwoStarThreshold(1)).toBe(34000);
// Level 5: 32,000 + (5 × 2,000) = 42,000expect(hud.getTwoStarThreshold(5)).toBe(42000);
});
test('should calculate 3-star threshold correctly per level', () => {
// Level 1: 66,000 + (1 × 6,000) = 72,000expect(hud.getThreeStarThreshold(1)).toBe(72000);
// Level 10: 66,000 + (10 × 6,000) = 126,000expect(hud.getThreeStarThreshold(10)).toBe(126000);
});
});
describe('star fill state updates', () => {
test('should show first star filled when score exceeds first threshold', () => {
hud.updateStarRating(10000, 1); // Below 34,000// Verify star 0 is empty (gray, low alpha)expect(hud.stars[0].tintTopLeft).toBe(0xcccccc);
expect(hud.stars[0].alpha).toBeLessThan(0.5);
});
test('should show second star filled when score exceeds threshold', () => {
hud.updateStarRating(35000, 1); // Above 34,000// Verify star 0 is filled (yellow, full alpha)expect(hud.stars[0].tintTopLeft).toBe(0xffcc00);
expect(hud.stars[0].alpha).toBe(1.0);
});
});
describe('animation behavior', () => {
test('should create tween when star state changes', () => {
const tweenSpy = vi.fn();
scene.tweens.add = tweenSpy;
hud.updateStarRating(0, 1); // Zero score
hud.updateStarRating(35000, 1); // Above first threshold// Should have created tweens for animationexpect(tweenSpy).toHaveBeenCalled();
});
test('should kill existing tweens before creating new ones', () => {
const killSpy = vi.fn();
scene.tweens.killTweensOf = killSpy;
// Update twice to trigger tween cleanup
hud.updateStarRating(10000, 1);
hud.updateStarRating(35000, 1);
// Should kill old tweens to prevent accumulationexpect(killSpy).toHaveBeenCalled();
});
});
});
Testing Real-time Updates
describe('real-time score updates', () => {
test('should update star display as score increases', () => {
const updateSpy = vi.fn();
hud.on('star-updated', updateSpy);
// Simulate score increases over time
hud.updateStarRating(10000, 1);
hud.updateStarRating(20000, 1);
hud.updateStarRating(34000, 1); // Should trigger star fillexpect(updateSpy).toHaveBeenCalledTimes(3);
});
test('should handle rapid score updates efficiently', () => {
const startTime = performance.now();
// Simulate 60 updates per secondfor (let i = 0; i < 60; i++) {
hud.updateStarRating(i * 100, 1);
}
const duration = performance.now() - startTime;
// Should complete in under 16ms (one frame)expect(duration).toBeLessThan(16);
});
});
Test Coverage Checklist for UI Components
Threshold calculations - Verify all level formulas correct
State transitions - Test empty → partial → full states
Tween creation - Verify animations fire on state changes
Tween cleanup - Ensure old tweens are killed
Performance - Rapid updates don't cause frame drops
Boundary conditions - Zero score, max score, negative values