Skip to main content

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.js files
  • 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

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:coverage to see what's missing
  • Read error messages carefully: Jest provides helpful failure messages