Testing Guide
January 27, 2026 · View on GitHub
Learn how to write comprehensive tests for framework packages.
Table of Contents
Testing Philosophy
Goals
- Prevent regressions - Tests catch breaking changes
- Document behavior - Tests show how code should work
- Enable refactoring - Tests allow confident code changes
- Quality assurance - Tests verify code meets requirements
Testing Levels
- Unit Tests - Test individual functions/classes
- Integration Tests - Test package interactions
- E2E Tests - Test full bot behavior (optional)
Test Structure
Directory Layout
packages/@ralphschuler/screeps-mynew/
├── src/
│ ├── Manager.ts
│ └── utils.ts
├── test/
│ ├── Manager.test.ts
│ ├── utils.test.ts
│ └── __mocks__/
│ └── Game.ts
├── jest.config.js
└── package.json
Jest Configuration
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/index.ts'
]
};
Unit Testing
Basic Test Structure
// test/Manager.test.ts
import { MyManager } from '../src/Manager';
describe('MyManager', () => {
let manager: MyManager;
beforeEach(() => {
manager = new MyManager();
});
afterEach(() => {
// Clean up
});
describe('constructor', () => {
it('should initialize with default options', () => {
expect(manager).toBeDefined();
expect(manager.options.debug).toBe(false);
});
it('should accept custom options', () => {
const customManager = new MyManager({ debug: true });
expect(customManager.options.debug).toBe(true);
});
});
describe('run', () => {
it('should process without errors', () => {
expect(() => manager.run()).not.toThrow();
});
});
});
Mocking Game Objects
// test/__mocks__/Game.ts
export const mockRoom = {
name: 'W1N1',
controller: {
my: true,
level: 5,
progress: 50000,
progressTotal: 100000
},
find: jest.fn(),
createConstructionSite: jest.fn()
};
export const mockGame = {
rooms: {
'W1N1': mockRoom
},
creeps: {},
cpu: {
getUsed: jest.fn(() => 5.0),
bucket: 10000
},
time: 12345
};
// Use in tests
import { mockGame, mockRoom } from './__mocks__/Game';
beforeEach(() => {
(global as any).Game = mockGame;
});
Testing with Screeps API
describe('SpawnManager', () => {
let mockSpawn: any;
beforeEach(() => {
mockSpawn = {
name: 'Spawn1',
room: mockRoom,
spawning: null,
spawnCreep: jest.fn(() => OK),
energy: 300,
energyCapacity: 300
};
});
it('should spawn a creep when requested', () => {
const manager = new SpawnManager();
const requests = [{
role: 'harvester',
priority: 100,
memory: { role: 'harvester' }
}];
manager.processSpawnQueue([mockSpawn], requests);
expect(mockSpawn.spawnCreep).toHaveBeenCalled();
});
});
Testing Pure Functions
// src/utils.ts
export function calculateBodyCost(body: BodyPartConstant[]): number {
const costs: Record<BodyPartConstant, number> = {
[MOVE]: 50,
[WORK]: 100,
[CARRY]: 50,
[ATTACK]: 80,
[RANGED_ATTACK]: 150,
[HEAL]: 250,
[CLAIM]: 600,
[TOUGH]: 10
};
return body.reduce((sum, part) => sum + costs[part], 0);
}
// test/utils.test.ts
import { calculateBodyCost } from '../src/utils';
describe('calculateBodyCost', () => {
it('should calculate cost for single part', () => {
expect(calculateBodyCost([MOVE])).toBe(50);
expect(calculateBodyCost([WORK])).toBe(100);
});
it('should calculate cost for multiple parts', () => {
const body = [MOVE, MOVE, WORK, CARRY];
expect(calculateBodyCost(body)).toBe(250); // 50+50+100+50
});
it('should return 0 for empty body', () => {
expect(calculateBodyCost([])).toBe(0);
});
});
Integration Testing
Testing Package Interactions
// test/integration/kernel.test.ts
import { Kernel } from '@ralphschuler/screeps-kernel';
import { SpawnManager } from '@ralphschuler/screeps-spawn';
describe('Kernel integration', () => {
let kernel: Kernel;
let spawnManager: SpawnManager;
beforeEach(() => {
kernel = new Kernel();
spawnManager = new SpawnManager();
});
it('should register and execute spawn process', () => {
const executeSpy = jest.fn();
kernel.registerProcess({
id: 'spawning',
execute: executeSpy,
cpuBudget: 0.5,
priority: 90
});
kernel.run();
expect(executeSpy).toHaveBeenCalled();
});
it('should respect CPU budgets', () => {
let totalCPU = 0;
const cpuBudget = 1.0;
kernel.registerProcess({
id: 'test',
execute: () => {
totalCPU += 0.5;
},
cpuBudget,
priority: 50
});
kernel.run();
// Should not exceed budget significantly
expect(totalCPU).toBeLessThanOrEqual(cpuBudget * 1.1);
});
});
Testing State Machines
describe('CreepStateMachine', () => {
it('should transition states correctly', () => {
const creep = createMockCreep();
const stateMachine = new CreepStateMachine(creep);
// Initial state
expect(stateMachine.currentState).toBe('harvesting');
// Transition when full
creep.store.getFreeCapacity(RESOURCE_ENERGY) = 0;
stateMachine.update();
expect(stateMachine.currentState).toBe('delivering');
// Transition when empty
creep.store.getUsedCapacity(RESOURCE_ENERGY) = 0;
stateMachine.update();
expect(stateMachine.currentState).toBe('harvesting');
});
});
Test Coverage
Coverage Requirements
Minimum thresholds:
- Branches: 80%
- Functions: 80%
- Lines: 80%
- Statements: 80%
Running Coverage
# Run tests with coverage
npm test -- --coverage
# View HTML coverage report
open coverage/index.html
Coverage Reports
-----------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
-----------------------|---------|----------|---------|---------|
All files | 85.23 | 82.14 | 88.46 | 85.67 |
src/ | 90.12 | 87.50 | 92.31 | 90.45 |
Manager.ts | 95.00 | 90.00 | 100.00 | 95.00 |
utils.ts | 85.71 | 80.00 | 88.89 | 86.36 |
-----------------------|---------|----------|---------|---------|
Improving Coverage
1. Test edge cases:
it('should handle empty input', () => {
expect(processArray([])).toEqual([]);
});
it('should handle null/undefined', () => {
expect(processValue(null)).toBe(null);
expect(processValue(undefined)).toBe(undefined);
});
2. Test error conditions:
it('should throw on invalid input', () => {
expect(() => processValue(-1)).toThrow();
});
3. Test all branches:
it('should handle bucket > 8000', () => {
Game.cpu.bucket = 9000;
expect(shouldRunExpensiveOp()).toBe(true);
});
it('should handle bucket < 2000', () => {
Game.cpu.bucket = 1000;
expect(shouldRunExpensiveOp()).toBe(false);
});
Best Practices
1. Descriptive Test Names
// Bad
it('works', () => { /* ... */ });
// Good
it('should spawn harvester when energy available', () => { /* ... */ });
2. Arrange-Act-Assert Pattern
it('should calculate optimal body parts', () => {
// Arrange
const energy = 550;
const role = 'harvester';
// Act
const body = calculateBody(role, energy);
// Assert
expect(body).toEqual([WORK, WORK, CARRY, MOVE]);
expect(calculateBodyCost(body)).toBeLessThanOrEqual(energy);
});
3. Use beforeEach for Setup
describe('Manager', () => {
let manager: Manager;
let mockRoom: any;
beforeEach(() => {
mockRoom = createMockRoom();
manager = new Manager();
});
// Tests...
});
4. Test One Thing Per Test
// Bad
it('should do everything', () => {
expect(manager.init()).toBe(true);
expect(manager.run()).toBe(OK);
expect(manager.cleanup()).toBe(true);
});
// Good
it('should initialize successfully', () => {
expect(manager.init()).toBe(true);
});
it('should run without errors', () => {
manager.init();
expect(manager.run()).toBe(OK);
});
5. Mock External Dependencies
// Mock cache
jest.mock('@ralphschuler/screeps-cache', () => ({
getCachedRoomObjects: jest.fn()
}));
import { getCachedRoomObjects } from '@ralphschuler/screeps-cache';
it('should use cached objects', () => {
(getCachedRoomObjects as jest.Mock).mockReturnValue([mockSource]);
const sources = manager.getSources(mockRoom);
expect(sources).toEqual([mockSource]);
expect(getCachedRoomObjects).toHaveBeenCalledWith(mockRoom, FIND_SOURCES, 100);
});
6. Test Async Code
it('should handle async operations', async () => {
const result = await manager.asyncOperation();
expect(result).toBe('success');
});
Related Documentation
- Package Development - Creating packages
- Release Process - Publishing workflow
- Jest Documentation - Testing framework
Last Updated: 2026-01-27
Framework Version: 0.1.0