Unit Testing Guidelines
Unit tests are the foundation of our testing strategy. They are small, fast, and test a single “unit” of code in isolation. Our primary tools for unit testing are Jest and React Testing Library.
What to Test
- React Components:
- Test that the component renders correctly given a set of props.
- Test that user interactions (like clicks or form inputs) trigger the correct events or state changes.
- Test conditional rendering logic (e.g., does the “Loading…” message appear when
isLoadingis true?).
- Utility Functions:
- Test that the function returns the correct output for a given input.
- Test edge cases (e.g., what happens if the input is
null,undefined, or an empty array?).
- API Routes / Server-side Logic:
- Test the logic of your server-side functions, mocking any external dependencies like databases or other APIs.
What NOT to Test
- Implementation Details: Don’t test the internal state or methods of a component. Test the component from the user’s perspective (i.e., what they see and can interact with).
- Third-Party Libraries: Don’t test that a third-party library (like a date picker) works. Assume it does. Test that your code is interacting with it correctly.
- Trivial Code: Don’t write tests for code that has no logic (e.g., a simple component that just renders a title).
Best Practices
The AAA Pattern: Arrange, Act, Assert
Structure your tests in three distinct parts:
- Arrange: Set up the test. Render the component with the necessary props, or create the inputs for your function.
- Act: Perform the action you want to test (e.g., click a button, call the function).
- Assert: Check that the outcome is what you expected.
Example (React Component):
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('increments the count when the button is clicked', () => {
// 1. Arrange
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const countDisplay = screen.getByText(/count is/i);
// 2. Act
fireEvent.click(button);
// 3. Assert
expect(countDisplay).toHaveTextContent('Count is 1');
});Writing Good Assertions
- Be specific:
expect(user.name).toBe('Alice')is better thanexpect(user).toBeDefined(). - Use semantic matchers: React Testing Library provides excellent, user-centric matchers.
- Prefer
getByRole,getByLabelText,getByText. - Avoid
getByTestIdunless there’s no other way to get the element.
- Prefer
Mocking
- Jest’s Mocking Functions: Use
jest.fn()to create mock functions andjest.spyOn()to spy on or mock existing functions. - Mocking Modules: Use
jest.mock('./path/to/module')to mock entire modules. This is essential for isolating your unit under test from its dependencies (like API clients or other services). - File-based Mocking: For mocking API responses, you can place mock files in a
__mocks__directory adjacent to the module you’re mocking.
File Location and Naming
- Test files should be located alongside the files they are testing.
- The file name should be
[filename].test.tsor[filename].test.tsx.
Example:
/components
/Button
- Button.tsx
- Button.test.tsx
By writing good unit tests, we can build a safety net that allows us to refactor code and add new features with confidence.