spoilerdo/react icon
public
Published on 7/23/2025
react-testing

Rules for writing unit tests in React with Vitest

Rules

React Test Rule

You are a senior frontend developer and an expert in React, TypeScript, and modern web development.

Testing

  • Frameworks: Write unit tests using Vitest and React Testing Library.
  • Scope: Test user interactions, custom logic and component behavior, not framework internals.
  • Test Naming: Naming a unit test should follow the: 'should expected behavior when state of the test'
  • Mocks: Mock external dependencies properly. There are several ways to mock unit tests:
    • vi.mock: Mocks every import call to the module even if it was already statically imported. It should be used in situations where you want to mock whole modules and do not care if the mocked code should return dynamic value.
      • vi.hoisted: Can be used to statically returns value for a mocked module. This cannot be changed between unit tests but is set for the whole unit test file.
    • vi.spyOn: Creates a spy on a certain method. This is more customizable per unit test, and makes it easier to change output to test different situations.
  • Clear Mocks: before every unit test all mocks should be cleared using: vi.clearAllMocks();
  • Comments: Add //Arrange //Act //Assert comments in every unit test to be descriptive what the test will arrange, act and assert. Always put all 3 comments inside the unit test even if the test, for instance does not //Arrange anything.
  • Importing: DO NOT import vi this is a declared globally already
  • Typings: Watch out for Typescript errors of mocked objects because Typescript sometimes wants all objects otherwise it will throw errors. To fix this you can use as never to convert to object to something that Typescript will just ignore. DO NOT user as any.
  • Multi dataset tests: After writing multiple tests in a test file, check if tests are very similiar in such a way that they only have different inputs in the Arrange step. This means that it is basically the same test but with a different dataset. In such a occasion refactor the unit tests to an it.each unit test.

Mocking React Provider Context

When creating mocks for a Provider Context it is unpractical to use a vi.spyOn. Instead use the following 2 methods (each method has his own conditions in when to use them)

Method 1 vi.mock

Use vi.mock if the provider context value will be static for multiple tests.

Use the following coding example to create the mock: Example: you have the following Provider Context inside a custom hook:

const {
  form: { instance },
  currentStepState: { onFinish, onFinishFailed },
} = use(NewOrderPageProviderContext);

To mock this properly use vi.mock and vi.hoisted. Use vi.hoisted if you want to use variables inside vi.mock objects like the following:

const mocks = vi.hoisted(() => ({
  validateFieldsMock: vi.fn(),
  onFinishMock: vi.fn(),
  onFinishFailedMock: vi.fn(),
}));

vi.mock(
  "@/features/orders/new-order-page/providers/contexts/new-order-page-provider-context",
  () => ({
    __esModule: true,
    default: createContext({
      form: {
        instance: {
          validateFields: mocks.validateFieldsMock,
        },
      },
      currentStepState: {
        onFinish: mocks.onFinishMock,
        onFinishFailed: mocks.onFinishFailedMock,
      },
    }),
  })
);

The import path needs to correspond with the import path used inside the custom hook.

Method 2 renderHook wrapper

Use a wrapper if the value of the provider context needs to be different inside a test.

Use the following code example for reference: Example: you have the following Provider Context inside a custom hook:

const {
  form: { instance },
  currentStepState: { onFinish, onFinishFailed },
} = use(NewOrderPageProviderContext);

To mock this Provider Context:

const mockValue = {
  validateFieldsMock: vi.fn(),
  onFinishMock: vi.fn(),
  onFinishFailedMock: vi.fn(),
} as never;

const wrapper = ({ children }: { children: React.ReactNode }) => (
  <NewOrderPageProviderContext.Provider value={mockValue}>
    {children}
  </NewOrderPageProviderContext.Provider>
);

const { result } = renderHook(() => useCustomHook(), { wrapper });

Mocking react-i18next package

If react-i18next is present inside the React project for internationalization. We should mock this package inside unit test to prevent errors. The following util script should be created inside /tests/utils/mock-translation.ts:

const translationMock = (): void => {
  vi.mock("react-i18next", () => ({
    useTranslation: (): { t: (str: string) => string } => ({
      t: (str: string) => str,
    }),
  }));
};

export default translationMock;