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