Jest 30
Version
jest@30.3.0 (from pnpm catalog), ts-jest@29.4.9
Note: client/main uses jest@~29.7.0 directly (not the catalog version) for Expo compatibility.
Critical Patterns
- Use
describe/it/expectstructure for all tests - Use
jest.mock()at the top of the file for module mocking (London School TDD) - Use
beforeEachto reset mocks:jest.clearAllMocks()orjest.resetAllMocks() - Use
async/awaitfor async tests -- never usedonecallbacks - Default
moduleFileExtensionsnow includes.mtsand.cts - Jest 30 uses
unrs-resolverfor faster module resolution (37% faster, 77% less memory) - Jest 30 warns about uncleaned globals -- clean up in
afterEach - Use
jest.fn<ReturnType, Args>()for typed mock functions - Use
jest.spyOn()for spying on methods without replacing them - Test files go in
tests/or colocated as*.test.tsnext to source
Must NOT Do
- NEVER use snapshot tests excessively -- prefer explicit assertions
- NEVER use
donecallback for async tests -- useasync/await - NEVER mock what you do not own without a wrapper (wrap third-party APIs)
- NEVER write tests that depend on execution order
- NEVER use
jest.setTimeout()globally -- set per test if needed - NEVER import from
jestdirectly (globals are available) - NEVER use
SpyInstancetype -- usejest.SpiedFunction(SpyInstance removed in Jest 30) - NEVER leave unresolved promises or timers -- Jest 30 detects and warns
Migration from Jest 29 (client/main)
jest.SpyInstancerenamed tojest.SpiedFunctionin Jest 30expect.addSnapshotSerializermoved to project configfakeTimers.legacyFakeTimersremoved -- use modern fake timers onlyjest-environment-jsdomupgraded to jsdom 26
Examples
Basic test with mocks (London School)
import { UserService } from '../user.service';
import { UserRepository } from '../user.repository';
jest.mock('../user.repository');
describe('UserService', () => {
let service: UserService;
const mockRepo = jest.mocked(new UserRepository());
beforeEach(() => {
jest.clearAllMocks();
service = new UserService(mockRepo);
});
it('should return user by id', async () => {
const expected = { id: '1', name: 'Test' };
mockRepo.findById.mockResolvedValue(expected);
const result = await service.getUser('1');
expect(result).toEqual(expected);
expect(mockRepo.findById).toHaveBeenCalledWith('1');
});
it('should throw when user not found', async () => {
mockRepo.findById.mockResolvedValue(null);
await expect(service.getUser('999')).rejects.toThrow('User not found');
});
});
Typed mock functions
const mockFetch = jest.fn<Promise<Response>, [string, RequestInit?]>();
jest.config.ts pattern (services)
import type { Config } from 'jest';
const config: Config = {
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/*.test.ts'],
testPathIgnorePatterns: ['/node_modules/', '\\.integration\\.test\\.ts$'],
transform: {
'^.+\\.ts$': ['ts-jest', { useESM: false }],
},
moduleNameMapper: {
'^@services/users/(.*)$': '<rootDir>/src/$1',
'^@packages/models/(.*)$': '<rootDir>/node_modules/@packages/models/src/$1',
},
moduleFileExtensions: ['ts', 'js', 'json'],
};
export default config;
Testing async error handling
it('should handle network errors gracefully', async () => {
mockClient.send.mockRejectedValue(new Error('Network timeout'));
const result = await service.fetchData();
expect(result).toEqual({ success: false, error: 'Network timeout' });
});
Source: migu-developer/financial-management — distributed by TomeVault.