Testing Best Practices
Writing tests is one thing. Writing good tests is another. After writing hundreds of tests, I have learned a few patterns that make the difference between tests that help and tests that just slow you down.
Here are the practices that actually matter.
FIRST Principles
Your tests should be Fast, Independent, Repeatable, Self-validating, and Timely. Fast means they run in milliseconds. Independent means one test does not depend on another. Repeatable means they give the same result every time. Self-validating means they pass or fail on their own. Timely means you write them at the right time.
// Good: fast, independent, repeatable
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
// Bad: depends on external state
let counter = 0;
it('increments', () => {
counter++;
expect(counter).toBe(1);
});
it('increments again', () => {
counter++;
expect(counter).toBe(2);
});
See the problem with the second example? If the first test fails or runs out of order, the second one fails too. Tests should never depend on execution order.
AAA Pattern
Arrange, Act, Assert. Set up your data, call the function, check the result. It is simple but keeps your tests readable.
it('calculates discount', () => {
// Arrange
const price = 100;
const discountPercent = 20;
// Act
const discount = calculateDiscount(price, discountPercent);
// Assert
expect(discount).toBe(20);
});
Test One Thing
Each test should verify one behavior. If you find yourself writing "and" in your test name, split it into two tests.
// Bad: tests two things
it('validates and saves user', () => {
const user = createUser('Alice');
expect(user.isValid).toBe(true);
expect(saveUser(user)).toBe(true);
});
// Good: each test does one thing
it('validates user name', () => {
const user = createUser('Alice');
expect(user.isValid).toBe(true);
});
it('saves valid user', () => {
const user = createUser('Alice');
expect(saveUser(user)).toBe(true);
});
Avoid Testing Implementation Details
Test what the code does, not how it does it. If you refactor the internal logic but the behavior stays the same, your tests should still pass.
Try it Yourself →Key Takeaways
- FIRST: Fast, Independent, Repeatable, Self-validating, Timely
- AAA: Arrange, Act, Assert pattern keeps tests readable
- Test one behavior per test case, avoid testing multiple things
- Never let tests depend on execution order
- Test behavior, not implementation details