Unit Testing Guidelines
Overview
Unit testing is mandatory for all code changes in The Wall project. Tests ensure component reliability, prevent regressions, and provide confidence when refactoring. The project maintains a high coverage target (100%) and employs strict testing standards across both web and native platforms.
File Structure and Naming
Test File Naming
Tests must be co-located with the component using the .test.jsx or .test.js extension:
ComponentName/
├── ComponentName.tsx # Component implementation
├── ComponentName.test.jsx # Unit tests (REQUIRED)
├── ComponentName.stories.tsx # Storybook stories (REQUIRED)
├── ComponentName.module.css # CSS Modules (web only)
├── ComponentName.styles.ts # StyleSheet (native only)
└── ComponentName.selectors.js # Test selectors (if needed)
Basic Component Test Structure
import { render, screen, fireEvent } from "@testing-library/react";
import { ComponentName } from "./ComponentName";
import { TEST_ID } from "./ComponentName.selectors";
import styles from "./ComponentName.module.css";
// Mock heavy dependencies
jest.mock("@ppb/the-wall-icons/GenericIcon/GenericIcon", () => ({
GenericIcon: jest.fn(() => <generic-icon-mock />),
}));
describe("ComponentName", () => {
beforeEach(jest.clearAllMocks);
it("should render correctly", () => {
render(<ComponentName label="Test" />);
const element = screen.queryByTestId(TEST_ID);
expect(element).not.toBeNull();
expect(element).toHaveClass(styles.container);
});
it("should handle user interactions", () => {
const onTap = jest.fn();
render(<ComponentName onTap={onTap} />);
const button = screen.getByRole("button");
fireEvent.click(button);
expect(onTap).toHaveBeenCalledTimes(1);
});
});
Testing Custom Hooks
import { renderHook, act } from "@testing-library/react";
import { useSwipe } from "./useSwipe";
describe("useSwipe", () => {
it("should return initial state", () => {
const ref = React.createRef();
const { result } = renderHook(() => useSwipe(ref));
expect(result.current).toStrictEqual({
horizontal: undefined,
vertical: undefined,
});
});
it("should detect swipe direction", () => {
const ref = React.createRef();
const { container } = render(<div ref={ref} />);
const { result } = renderHook(() => useSwipe(ref));
act(() => {
fireEvent.mouseDown(container, { clientX: 0, clientY: 0 });
fireEvent.mouseMove(container, { clientX: 60, clientY: 0 });
fireEvent.mouseUp(container, { clientX: 60, clientY: 0 });
});
expect(result.current.horizontal).toBe("Right");
});
it("should cleanup event listeners on unmount", () => {
const ref = React.createRef();
const { container } = render(<div ref={ref} />);
const spyRemove = jest.spyOn(container, "removeEventListener");
const { unmount } = renderHook(() => useSwipe(ref));
unmount();
expect(spyRemove).toHaveBeenCalledTimes(4);
});
});
Best Practices
✅ Do
- Clear test descriptions: Use descriptive
it()statements - Arrange-Act-Assert: Structure tests clearly
- Clean up: Use
beforeEach(jest.clearAllMocks)consistently - Test user behavior: Focus on what users see and do
- Prefer accessibility queries: Use
getByRole,getByLabelText, etc.- Use testID selectors when necessary: Import from
.selectors.jsfiles
- Use testID selectors when necessary: Import from
- Mock heavy deps: Mock icons, complex components, external APIs
- Test edge cases: Empty states, long text, disabled states, etc.
- Verify mock calls: Use
toHaveBeenCalledWith()to check arguments - Keep tests isolated: Each test should be independent
❌ Don't
- Don't test implementation details: Avoid testing internal state
- Don't skip cleanup: Always clear mocks between tests
- Don't test libraries: Trust that React, Jest, etc. work
- Don't use
.only()or.skip(): Remove before committing - Don't ignore console errors: Fix them, don't suppress them
- Don't make tests interdependent: Each test must run in isolation
Additional Resources
- Contributing Guide
- Defining Element Selectors
- Testing Library Docs
- Jest Documentation
- React Native Testing Library
Getting Help
If you have questions or encounter issues:
- Slack Channel: #ask-the-wall
- Review existing tests: Check similar components for patterns
- Check test coverage: Run
yarn test:coverageto see what's missing - Read error messages carefully: Jest provides helpful failure messages