Testingmediumunit
Follow mocking best practices
rule · mock-best-practices
Effective mocking isolates the code under test while maintaining realistic test scenarios. Over-mocking leads to brittle tests that pass when they should fail.
Code Examples
Module Mocking
JavaScript
// Mock entire module
jest.mock('./api', () => ({
fetchUser: jest.fn().mockResolvedValue({ id: 1, name: 'Test' })
}))
// Mock specific exports
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
formatDate: jest.fn().mockReturnValue('2024-01-01')
}))Function Mocking
JavaScript
import { fetchUser } from './api'
jest.mock('./api')
const mockFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>
beforeEach(() => {
mockFetchUser.mockClear()
})
test('loads user data', async () => {
mockFetchUser.mockResolvedValueOnce({ id: 1, name: 'John' })
await loadUserProfile(1)
expect(mockFetchUser).toHaveBeenCalledWith(1)
})Spy on Methods
JavaScript
test('logs errors to console', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleError(new Error('Test'))
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Test')
)
consoleSpy.mockRestore()
})Why It Matters
Proper mocking isolates units under test while keeping tests realistic—over-mocking creates false confidence when tests pass but production breaks.
When to Mock
| Mock | Do not Mock |
|---|---|
| External APIs | Pure functions |
| Database calls | Business logic |
| File system | Data transformations |
| Time/Date | Simple utilities |
| Network requests | React components (usually) |
Common Mistakes
Over-Mocking
JavaScript
// ❌ Bad: Mocking the thing you're testing
jest.mock('./validateEmail')
test('validates email', () => {
// This tests nothing!
expect(validateEmail('test@test.com')).toBe(true)
})
// ✅ Good: Test the actual implementation
test('validates email format', () => {
expect(validateEmail('test@test.com')).toBe(true)
expect(validateEmail('invalid')).toBe(false)
})Missing Cleanup
JavaScript
// ❌ Bad: Mocks leak between tests
test('first test', () => {
jest.spyOn(Date, 'now').mockReturnValue(1000)
// Forgot to restore!
})
test('second test', () => {
// Date.now is still mocked!
})
// ✅ Good: Always cleanup
afterEach(() => {
jest.restoreAllMocks()
})Implementation Details
JavaScript
// ❌ Bad: Testing implementation
test('calls internal method', () => {
const spy = jest.spyOn(component, '_privateMethod')
component.doSomething()
expect(spy).toHaveBeenCalled()
})
// ✅ Good: Testing behavior
test('updates UI when action completes', () => {
component.doSomething()
expect(screen.getByText('Success')).toBeInTheDocument()
})MSW for API Mocking
JavaScript
import { setupServer } from 'msw/node'
import { rest } from 'msw'
const server = setupServer(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.json({ id: req.params.id, name: 'Test User' }))
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('fetches user data', async () => {
render(<UserProfile userId="1" />)
await screen.findByText('Test User')
})Standards
- Use these references as the standard for how the test or monitoring strategy should behave in the shipped workflow.
- Check the implementation against Playwright Docs before treating the rule as satisfied.
- Check the implementation against Testing Library Guiding Principles before treating the rule as satisfied.
Verification
- Run the relevant test or CI step locally and confirm it fails when the rule is violated.
- Ensure the automation blocks regressions instead of only printing warnings.
- Cover at least one representative high-risk flow, component, or route.
- Keep thresholds or assertions in version control so changes remain reviewable.