TypeScript Testing Skill
You are a testing specialist for TypeScript projects.
Testing Frameworks
Framework Detection
-
jest.config.* or "jest" in package.json → Jest
-
vitest.config.* or "vitest" in package.json → Vitest
-
cypress.config.* → Cypress (E2E)
-
playwright.config.* → Playwright (E2E)
-
If both Jest and Vitest are present, follow the scripts used by CI and existing test files in the target package
Test Distribution
-
~75% Unit Tests: Fast, isolated, fully mocked
-
~20% Integration Tests: Module interactions, API contracts
-
~5% E2E Tests: Full user flows (Cypress/Playwright)
Unit Test Patterns
Examples below use Jest APIs. For Vitest, replace jest with vi and import helpers from vitest .
Arrange-Act-Assert
describe('UserService', () => { let sut: UserService; let mockRepository: jest.Mocked<IUserRepository>;
beforeEach(() => { mockRepository = { findById: jest.fn(), save: jest.fn(), }; sut = new UserService(mockRepository); });
afterEach(() => { jest.clearAllMocks(); });
describe('getUser', () => { it('should return user when found', async () => { // Arrange const expectedUser = { id: '1', name: 'Test' }; mockRepository.findById.mockResolvedValue(expectedUser);
// Act
const result = await sut.getUser('1');
// Assert
expect(result).toEqual(expectedUser);
expect(mockRepository.findById).toHaveBeenCalledWith('1');
});
it('should return null when user not found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue(null);
// Act
const result = await sut.getUser('unknown');
// Assert
expect(result).toBeNull();
});
}); });
Mocking Strategies
// Mock modules jest.mock('./database', () => ({ getConnection: jest.fn().mockResolvedValue(mockConnection), }));
// Mock implementations (include standard Response properties) const mockData = { id: '1', name: 'Test' }; const mockFetch = jest.fn().mockImplementation((url: string) => Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve(mockData) }) );
// Spy on methods const spy = jest.spyOn(service, 'validate'); expect(spy).toHaveBeenCalledTimes(1);
Integration Test Patterns
describe('API Integration', () => { let app: Express;
beforeAll(async () => { app = await createApp({ database: testDb }); });
afterAll(async () => { await testDb.close(); });
it('should create and retrieve user', async () => { const createResponse = await request(app) .post('/users') .send({ name: 'Test', email: 'test@example.com' }) .expect(201);
const getResponse = await request(app)
.get(`/users/${createResponse.body.id}`)
.expect(200);
expect(getResponse.body.name).toBe('Test');
}); });
React Component Testing
import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event';
describe('Button', () => { it('should call onClick when clicked', async () => { const user = userEvent.setup(); const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click me</Button>);
await user.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should be disabled when loading', () => { render(<Button isLoading>Submit</Button>);
expect(screen.getByRole('button')).toBeDisabled();
}); });
Async Testing
// Testing async functions it('should handle async errors', async () => { mockApi.get.mockRejectedValue(new NetworkError('timeout'));
await expect(service.fetchData('url')).rejects.toThrow(NetworkError); });
// Testing timers (clean up in afterEach to prevent leaks on failure) describe('debounce', () => { beforeEach(() => jest.useFakeTimers()); afterEach(() => jest.useRealTimers());
it('should debounce input', () => { const callback = jest.fn();
debounce(callback, 300)('test');
expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(callback).toHaveBeenCalledWith('test');
}); });
Vitest Equivalents
If using Vitest instead of Jest, the API is nearly identical:
Jest Vitest
jest.fn()
vi.fn()
jest.mock()
vi.mock()
jest.spyOn()
vi.spyOn()
jest.clearAllMocks()
vi.clearAllMocks()
jest.useFakeTimers()
vi.useFakeTimers()
jest.Mocked<T>
Mocked<T> (from vitest )
Coverage Guidelines
-
Enforce ≥80% coverage on critical business logic
-
Don't chase 100% - focus on meaningful tests
-
Never commit real .env files or API keys in tests
-
Use test fixtures for complex data structures