Quiz

What are some best practices for writing maintainable and effective tests in JavaScript?

Topics
JavaScriptTesting

TL;DR

Write tests around observable behavior and important risks. Give each test a name that describes the scenario and outcome, make failures easy to diagnose, control nondeterministic boundaries, and clean up all mutated state. Use real collaborators when they are fast and deterministic; mock only boundaries where control or isolation adds value. Explicit expected values are useful documentation—avoid duplicating production logic to calculate the expected result.


Best practices for writing maintainable and effective tests

Write clear and descriptive test names

  • Use names that clearly describe the behavior being tested
  • Avoid abbreviations and keep names meaningful

Keep tests focused

  • Ensure each test case focuses on a single behavior or functionality
  • Avoid testing multiple things in a single test

Use the AAA pattern (Arrange, Act, Assert)

  • Arrange: Set up the initial state and dependencies
  • Act: Execute the behavior being tested
  • Assert: Verify the outcome
test('should add two numbers correctly', () => {
// Arrange
const a = 1;
const b = 2;
// Act
const result = add(a, b);
// Assert
expect(result).toBe(3);
});

Keep expected results independent

  • Use explicit values when they clarify the contract.
  • Do not recreate the production algorithm inside the test, because the same mistake can appear in both.
test('grants free shipping at the $50 boundary', () => {
expect(calculateShipping(50)).toBe(0);
});

Control external dependencies deliberately

  • Stub destructive, slow, nondeterministic, or unavailable services in unit tests.
  • Prefer a real in-memory collaborator or integration test when a mock would merely reproduce another module's implementation.
jest.mock('axios');
test('should fetch data from API', async () => {
const data = { id: 1, name: 'John Doe' };
axios.get.mockResolvedValue({ data });
const result = await fetchData();
expect(result).toEqual(data);
});

Keep tests isolated

  • Ensure tests do not depend on each other
  • Reset state and clean up after each test
afterEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
});

Also remove DOM nodes and listeners, reset servers or databases, and restore globals. A test should produce the same result alone, after any other test, and during parallel execution.

Regularly review and refactor tests

  • Keep tests up-to-date with changes in the codebase
  • Remove redundant or outdated tests
  • Refactor tests to improve readability and maintainability

Use test coverage tools

  • Measure test coverage to identify untested parts of the codebase
  • Aim for high coverage but prioritize meaningful tests over 100% coverage

Coverage reveals code that did not execute; it cannot prove that assertions are useful. Review uncovered high-risk branches before chasing a repository-wide percentage.

Write tests before fixing bugs

  • Reproduce the bug with a failing test
  • Fix the bug and ensure the test passes

Use a consistent style

  • Follow a consistent style and conventions for writing tests
  • Use linters and formatters to enforce consistency

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise 1 of 2
Check your understanding Exercise 1 of 2

A test mocks every helper, reproduces the production calculation to derive its expected value, and leaves fake timers enabled. Explain how to improve it.