Skip to content

Test Failures

Quick reference for diagnosing and fixing common test failures.

Terminal window
# Run failing test with verbose output
pnpm run test -- --verbose failing-test.test.ts
# Run single test
pnpm run test -- -t "test name pattern"
# Run with coverage
pnpm run test -- --coverage
# Clear Jest cache
pnpm run test -- --clearCache
# Run in band (sequential, not parallel)
pnpm run test -- --runInBand
# Debug test
node --inspect-brk node_modules/.bin/jest --runInBand failing-test.test.ts

Error:

Cannot find module '@banyanai/platform-cqrs' from 'src/commands/CreateUserHandler.ts'

Fix - Check Package Installation:

Terminal window
# Install dependencies
pnpm install
# Rebuild packages
pnpm run build
# Check package.json
cat package.json | jq '.dependencies'

Fix - Verify Import Path:

// ❌ WRONG: Missing .js extension
import { CommandHandler } from './decorators';
// ✓ CORRECT: Include .js extension (required for ES modules)
import { CommandHandler } from './decorators.js';

Fix - Check tsconfig.json:

{
"compilerOptions": {
"module": "Node16", // Required
"moduleResolution": "Node16", // Required
"esModuleInterop": true
}
}

Error:

Timeout - Async callback was not invoked within the 5000 ms timeout

Fix - Increase Timeout:

// For specific test
it('should process large dataset', async () => {
// Test code
}, 30000); // 30 second timeout
// For all tests in file
jest.setTimeout(30000);
// In jest.config.js
module.exports = {
testTimeout: 30000
};

Fix - Ensure Async Completion:

// ❌ WRONG: Missing await
it('should create user', () => {
createUser({ email: 'test@example.com' }); // Not awaited!
});
// ✓ CORRECT: Await async operations
it('should create user', async () => {
await createUser({ email: 'test@example.com' });
});

Error:

Error: Connection terminated unexpectedly
Error: database "platform_test" does not exist

Fix - Setup Test Database:

Terminal window
# Create test database
docker exec postgres psql -U postgres -c "CREATE DATABASE platform_test;"
# Run migrations
pnpm run db:migrate:test
# Initialize schema
pnpm run db:schema:test

Fix - Use Test Database URL:

// jest.setup.ts or test file
process.env.DATABASE_URL = 'postgresql://postgres:postgres@localhost:5432/platform_test';

Fix - Clean Up After Tests:

beforeEach(async () => {
// Clear database before each test
await clearDatabase();
});
afterAll(async () => {
// Close connections
await pool.end();
await messageBus.disconnect();
});

Error:

Expected mock function to have been called, but it was not called

Fix - Verify Mock Setup:

// ❌ WRONG: Mock after import
import { UserRepository } from './UserRepository.js';
jest.mock('./UserRepository.js');
// ✓ CORRECT: Mock before import
jest.mock('./UserRepository.js');
import { UserRepository } from './UserRepository.js';

Fix - Reset Mocks Between Tests:

beforeEach(() => {
jest.clearAllMocks(); // Clear call history
jest.resetAllMocks(); // Reset mock implementation
});

Fix - Check Mock Implementation:

// Define mock properly
const mockUserRepository = {
findById: jest.fn(),
save: jest.fn()
};
// Provide implementation
mockUserRepository.findById.mockResolvedValue({
userId: 'user-123',
email: 'test@example.com'
});
// Verify calls
expect(mockUserRepository.findById).toHaveBeenCalledWith('user-123');
expect(mockUserRepository.findById).toHaveBeenCalledTimes(1);

Symptom: Tests pass sometimes, fail other times

Fix - Avoid Time-Dependent Tests:

// ❌ WRONG: Depends on current time
it('should set creation date', () => {
const user = new User();
expect(user.createdAt).toBe(new Date()); // Flaky! Time changes
});
// ✓ CORRECT: Use fake timers or verify range
it('should set creation date', () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01'));
const user = new User();
expect(user.createdAt).toEqual(new Date('2024-01-01'));
jest.useRealTimers();
});
// Or verify it's recent
it('should set creation date', () => {
const before = new Date();
const user = new User();
const after = new Date();
expect(user.createdAt.getTime()).toBeGreaterThanOrEqual(before.getTime());
expect(user.createdAt.getTime()).toBeLessThanOrEqual(after.getTime());
});

Fix - Avoid Race Conditions:

// ❌ WRONG: Parallel async operations without coordination
it('should process items', async () => {
await Promise.all([
processItem(1),
processItem(2),
processItem(3)
]);
expect(results).toHaveLength(3); // Flaky! Results may not be ready
});
// ✓ CORRECT: Wait for completion
it('should process items', async () => {
const results = await Promise.all([
processItem(1),
processItem(2),
processItem(3)
]);
expect(results).toHaveLength(3);
});

Fix - Use waitFor for Async Updates:

import { waitFor } from '@testing-library/react';
it('should update read model', async () => {
await commandBus.execute(new CreateUserCommand('user-123', 'test@example.com'));
// Wait for projection to complete
await waitFor(async () => {
const user = await UserReadModel.findById('user-123');
expect(user).toBeDefined();
expect(user.email).toBe('test@example.com');
}, { timeout: 5000 });
});

Error:

Cannot decorate class with @CommandHandler: CreateUserCommand is not defined

Cause: Using import type for decorator argument

Fix - Use Value Import:

// ❌ WRONG: Type-only import can't be used in decorators
import type { CreateUserCommand } from './commands.js';
@CommandHandler(CreateUserCommand) // Error! Can't use type
export class CreateUserHandler {}
// ✓ CORRECT: Value import
import { CreateUserCommand } from './commands.js';
@CommandHandler(CreateUserCommand) // Works!
export class CreateUserHandler {}

Issue: Tests run but coverage below threshold

Fix - Check Untested Paths:

Terminal window
# Generate coverage report
pnpm run test -- --coverage
# View HTML report
open coverage/lcov-report/index.html
# Check specific file coverage
pnpm run test -- --coverage --collectCoverageFrom="src/handlers/CreateUserHandler.ts"

Fix - Test Error Paths:

describe('CreateUserHandler', () => {
it('should create user successfully', async () => {
// Happy path ✓
const result = await handler.handle(validCommand);
expect(result.userId).toBeDefined();
});
it('should throw error for duplicate email', async () => {
// Error path ✓
await expect(
handler.handle(duplicateEmailCommand)
).rejects.toThrow('Email already exists');
});
it('should throw error for invalid data', async () => {
// Validation error path ✓
await expect(
handler.handle(invalidCommand)
).rejects.toThrow('Validation failed');
});
});

Fix - Test Edge Cases:

describe('User aggregate', () => {
it('should handle empty string name', () => {
// Edge case
expect(() => user.setName('')).toThrow('Name cannot be empty');
});
it('should handle very long name', () => {
const longName = 'a'.repeat(1000);
expect(() => user.setName(longName)).toThrow('Name too long');
});
it('should handle special characters in name', () => {
user.setName('John O\'Brien');
expect(user.name).toBe('John O\'Brien');
});
});

Error:

AggregateNotFoundError: User with ID test-user not found

Fix - Initialize Event Store Schema:

beforeAll(async () => {
// Initialize event store schema
const eventStore = new PostgresEventStore(testDbConfig);
await eventStore.initializeSchema();
});

Fix - Create Test Aggregate:

it('should update user', async () => {
// Create aggregate first
const user = new User('test-user-123');
user.register('test@example.com', 'password');
await aggregateAccess.save(user, 'test-correlation-id');
// Now test update
const loaded = await aggregateAccess.getById('test-user-123');
loaded.updateEmail('new@example.com');
await aggregateAccess.save(loaded, 'test-correlation-id');
// Verify
const updated = await aggregateAccess.getById('test-user-123');
expect(updated.email).toBe('new@example.com');
});

Symptom: Tests slow down over time, memory usage increases

Fix - Clean Up Resources:

afterEach(async () => {
// Close database connections
await pool.end();
// Disconnect message bus
await messageBus.disconnect();
// Clear event listeners
eventEmitter.removeAllListeners();
// Clear timers
jest.clearAllTimers();
});

Fix - Use beforeEach/afterEach:

describe('UserService', () => {
let service: UserService;
let pool: Pool;
beforeEach(() => {
pool = new Pool(config);
service = new UserService(pool);
});
afterEach(async () => {
await pool.end(); // Clean up after each test
});
it('test 1', async () => {
// Uses fresh service and pool
});
it('test 2', async () => {
// Uses fresh service and pool
});
});
// Enable debug logging
process.env.LOG_LEVEL = 'debug';
process.env.DEBUG = '*';
// Log test data
console.log('Test input:', JSON.stringify(input, null, 2));
console.log('Test output:', JSON.stringify(output, null, 2));
// Use debugger
it('should process data', async () => {
debugger; // Breakpoint here
const result = await processData(input);
expect(result).toBeDefined();
});
// Run with node inspector
// node --inspect-brk node_modules/.bin/jest --runInBand test.ts
  • All imports have .js extension
  • Async functions use await
  • Mocks defined before imports
  • Database cleaned between tests
  • Connections closed in afterAll/afterEach
  • Time-dependent code uses fake timers
  • No race conditions in parallel tests
  • Error paths tested
  • Edge cases covered
  • Coverage >90% for all metrics
jest.config.js
module.exports = {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
tsconfig: {
experimentalDecorators: true,
emitDecoratorMetadata: true
}
}
]
},
testEnvironment: 'node',
testTimeout: 30000,
coverageThreshold: {
global: {
branches: 90,
functions: 90,
lines: 90,
statements: 90
}
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts',
'!src/__tests__/**',
'!src/__mocks__/**'
]
};
  1. Always clean up resources:
afterEach(async () => {
await pool.end();
await messageBus.disconnect();
});
  1. Use fake timers for time-dependent code:
jest.useFakeTimers();
jest.setSystemTime(new Date('2024-01-01'));
  1. Avoid test interdependence:
// Each test should be independent
beforeEach(() => {
// Fresh state for each test
});
  1. Test both happy and error paths:
it('succeeds with valid data', async () => { });
it('fails with invalid data', async () => { });
  1. Use descriptive test names:
// ❌ WRONG
it('works', () => {});
// ✓ CORRECT
it('should create user with valid email and return userId', async () => {});