principleMajorpending
Principle: Test behavior, not implementation
Viewed 0 times
test-behaviorimplementation-detailrefactoringbrittle-testsmocking
Problem
Tests that check internal implementation details (which functions were called, internal state, private methods) break whenever you refactor, even when behavior is unchanged.
Solution
Write tests that verify WHAT the code does, not HOW it does it:
// BAD - testing implementation:
test('creates user', () => {
const spy = jest.spyOn(db, 'insert');
createUser({ name: 'Alice' });
expect(spy).toHaveBeenCalledWith('users', { name: 'Alice', id: expect.any(String) });
});
// Breaks if you change from insert to upsert, even though behavior is same
// GOOD - testing behavior:
test('creates user', async () => {
const user = await createUser({ name: 'Alice' });
expect(user.name).toBe('Alice');
expect(user.id).toBeDefined();
const fetched = await getUser(user.id);
expect(fetched.name).toBe('Alice');
});
// Survives any refactoring that preserves behavior
Guidelines:
// BAD - testing implementation:
test('creates user', () => {
const spy = jest.spyOn(db, 'insert');
createUser({ name: 'Alice' });
expect(spy).toHaveBeenCalledWith('users', { name: 'Alice', id: expect.any(String) });
});
// Breaks if you change from insert to upsert, even though behavior is same
// GOOD - testing behavior:
test('creates user', async () => {
const user = await createUser({ name: 'Alice' });
expect(user.name).toBe('Alice');
expect(user.id).toBeDefined();
const fetched = await getUser(user.id);
expect(fetched.name).toBe('Alice');
});
// Survives any refactoring that preserves behavior
Guidelines:
- Test public interfaces, not private methods
- Assert on outputs and side effects, not internal calls
- Mock at boundaries (network, database), not between your own modules
- If a test breaks during refactoring but behavior is unchanged, the test was wrong
Why
Implementation-coupled tests create a maintenance burden and discourage refactoring. Behavior tests give you confidence to change internals freely.
Revisions (0)
No revisions yet.