Follow the Arrange-Act-Assert pattern:
test('should validate user input correctly', async () => {
// Arrange
const validInput = 'test@example.com';
const invalidInput = 'not-an-email';
// Act
const validResult = await validateInput(validInput);
const invalidResult = await validateInput(invalidInput);
// Assert
expect(validResult.isValid).toBe(true);
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.error).toContain('Invalid email');
});// ❌ Poor test names
test('crypto test');
test('it works');
// ✅ Good test names
test('should generate unique ephemeral keypairs for each pairing request');
test('should throw error when encrypting with invalid public key');
test('should handle network timeout during key exchange gracefully');Use describe blocks to organize related tests:
describe('PairingFlow Component', () => {
describe('Initial State', () => {
test('should show initiator option by default');
test('should have all required buttons disabled initially');
});
describe('Responder Flow', () => {
test('should validate pairing code format');
test('should show error for expired codes');
});
describe('Error Handling', () => {
test('should recover from network failures');
test('should timeout after maximum wait time');
});
});Every cryptographic function must test:
- Valid input handling
- Invalid input rejection
- Error conditions
- Key generation uniqueness
- Proper cleanup of sensitive data
describe('crypto service security', () => {
test('should generate unique keypairs', async () => {
const keys1 = await crypto.generateKeypair();
const keys2 = await crypto.generateKeypair();
expect(keys1.privateKey).not.toBe(keys2.privateKey);
expect(keys1.publicKey).not.toBe(keys2.publicKey);
});
test('should reject malformed public keys', async () => {
await expect(crypto.encrypt('data', 'invalid-key'))
.rejects.toThrow('Invalid public key');
});
test('should clear sensitive data from memory', async () => {
const keys = await crypto.generateEphemeralKeypair();
await crypto.clearEphemeralKeys();
// Verify keys are no longer accessible
expect(() => crypto.getEphemeralPrivateKey())
.toThrow('No ephemeral keys available');
});
});Test all security aspects of device pairing:
describe('pairing protocol security', () => {
test('should reject expired pairing codes', async () => {
const code = await pairingCode.generate();
// Mock time passage
vi.setSystemTime(new Date(Date.now() + 11 * 60 * 1000)); // 11 minutes
await expect(pairingCode.validate(code))
.rejects.toThrow('Pairing code expired');
});
test('should limit pairing attempts', async () => {
const invalidCode = 'INVALID';
// Try multiple invalid attempts
for (let i = 0; i < 5; i++) {
await expect(pairingCode.validate(invalidCode))
.rejects.toThrow('Invalid pairing code');
}
// Should be rate limited
await expect(pairingCode.validate(invalidCode))
.rejects.toThrow('Too many attempts');
});
});Test components from the user's perspective:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('user can add bookmark with all details', async () => {
const user = userEvent.setup();
render(<BookmarkForm onSave={mockSave} />);
// User actions
await user.type(screen.getByLabelText(/title/i), 'My Bookmark');
await user.type(screen.getByLabelText(/url/i), 'https://example.com');
await user.type(screen.getByLabelText(/description/i), 'Great resource');
await user.type(screen.getByLabelText(/tags/i), 'learning, reference');
await user.click(screen.getByRole('button', { name: /save/i }));
// Verify behavior
expect(mockSave).toHaveBeenCalledWith({
title: 'My Bookmark',
url: 'https://example.com',
description: 'Great resource',
tags: ['learning', 'reference']
});
});Include accessibility checks in component tests:
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('bookmark form is accessible', async () => {
const { container } = render(<BookmarkForm />);
// Check for accessibility violations
const results = await axe(container);
expect(results).toHaveNoViolations();
// Test keyboard navigation
const titleInput = screen.getByLabelText(/title/i);
await user.tab();
expect(titleInput).toHaveFocus();
});Always test error conditions:
test('shows validation errors for invalid input', async () => {
const user = userEvent.setup();
render(<BookmarkForm onSave={mockSave} />);
// Submit without required fields
await user.click(screen.getByRole('button', { name: /save/i }));
// Verify error messages
expect(screen.getByText(/title is required/i)).toBeInTheDocument();
expect(screen.getByText(/url is required/i)).toBeInTheDocument();
// Verify form was not submitted
expect(mockSave).not.toHaveBeenCalled();
});// Good mocking practice
vi.mock('../services/crypto', () => ({
generateKeypair: vi.fn().mockResolvedValue({
publicKey: 'mock-public-key',
privateKey: 'mock-private-key'
}),
encrypt: vi.fn().mockResolvedValue('encrypted-data'),
decrypt: vi.fn().mockResolvedValue('decrypted-data')
}));
// Reset mocks between tests
beforeEach(() => {
vi.clearAllMocks();
});// Mock fetch for API calls
global.fetch = vi.fn().mockImplementation((url) => {
if (url.includes('/api/bookmarks')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve([])
});
}
return Promise.reject(new Error('Unmocked URL'));
});// Use fake timers for time-dependent tests
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test('expires after timeout', async () => {
const promise = serviceWithTimeout();
// Advance time
vi.advanceTimersByTime(30000); // 30 seconds
await expect(promise).rejects.toThrow('Timeout');
});- New Code: 90%+ coverage required
- Security Code: 95%+ coverage required
- Critical Paths: 100% coverage preferred
- Overall Project: Maintain 80%+ coverage
Focus on meaningful coverage:
// ❌ Poor coverage - only tests happy path
test('saves bookmark', () => {
const bookmark = { title: 'Test', url: 'https://test.com' };
const result = saveBookmark(bookmark);
expect(result).toBe(true);
});
// ✅ Good coverage - tests multiple scenarios
describe('saveBookmark', () => {
test('saves valid bookmark successfully', () => {
const bookmark = { title: 'Test', url: 'https://test.com' };
expect(saveBookmark(bookmark)).toBe(true);
});
test('throws error for invalid URL', () => {
const bookmark = { title: 'Test', url: 'invalid-url' };
expect(() => saveBookmark(bookmark)).toThrow('Invalid URL');
});
test('handles storage failures gracefully', () => {
// Mock storage failure
vi.spyOn(localStorage, 'setItem').mockImplementation(() => {
throw new Error('Storage full');
});
const bookmark = { title: 'Test', url: 'https://test.com' };
expect(() => saveBookmark(bookmark)).toThrow('Storage full');
});
});Use /* c8 ignore */ sparingly and document why:
// ✅ Acceptable - development only
/* c8 ignore next 3 */
if (process.env.NODE_ENV === 'development') {
console.log('Debug information:', data);
}
// ✅ Acceptable - unreachable error case
/* c8 ignore next 2 */
default:
throw new Error('Unreachable code');
// ❌ Not acceptable - should be tested
/* c8 ignore next 5 */
if (userRole === 'admin') {
return performAdminAction();
}Ensure tests pass before committing:
# Run in pre-commit hook
npm run test:coverage:check
npm run test:security
npm run lint
npm run format:checkEvery pull request must:
- Pass all existing tests
- Add tests for new functionality
- Maintain coverage thresholds
- Update documentation if needed
- Review and update test data
- Remove obsolete tests
- Refactor duplicated test code
- Monitor and fix flaky tests
// Mark known flaky tests
test.skip('flaky test - investigating timeout issues', () => {
// Test implementation
});
// Or retry flaky tests
test.describe.configure({ retries: 2 });
test('potentially flaky network test', async () => {
// Implementation with retries
});Before submitting:
- Tests follow naming conventions
- All code paths are tested
- Error conditions are handled
- Tests are focused and independent
- Mock cleanup is proper
- Performance implications considered
- Accessibility tested (for UI components)
- Security aspects covered (for sensitive code)
When reviewing:
- Test quality and coverage
- Appropriate test types used
- Mock usage is reasonable
- Tests are maintainable
- Performance impact acceptable
- Security considerations addressed
- Documentation updated if needed