close

Mocking

Mocking lets you replace dependencies in tests, control return values, and assert how functions or modules are called. Rstest provides different mocking APIs for functions, object methods, ESM modules, CommonJS modules, and object trees.

Mock modules

If a dependency is loaded through the module system, you can choose different APIs based on the module type and mocking behavior.

Mock ESM modules

If a dependency is loaded through import, you can use rstest.mock() or rstest.doMock().

Use rstest.mock()

rstest.mock() is hoisted to the top of the file. It is useful when the dependency should be replaced before the module under test runs.

user-service.test.ts
import { expect, rstest, test } from '@rstest/core';
import { loadUserName } from './user-service';
import { fetchUser } from './api';

rstest.mock('./api', () => ({
  fetchUser: rstest.fn().mockResolvedValue({ id: '1', name: 'Alice' }),
}));

test('returns the fetched user name', async () => {
  await expect(loadUserName('1')).resolves.toBe('Alice');
  expect(fetchUser).toHaveBeenCalledWith('1');
});

Use rstest.doMock()

Note that rstest.doMock() is not hoisted and only takes effect after it runs. It is useful when earlier import statements should keep the real implementation and later ones should use the mock.

feature.test.ts
import { expect, rstest, test } from '@rstest/core';
import { readFeatureFlag } from './feature';

test('only mocks later imports', async () => {
  expect(readFeatureFlag()).toBe('real');

  rstest.doMock('./feature', () => ({
    readFeatureFlag: () => 'mocked',
  }));

  const { readFeatureFlag: mockedReadFeatureFlag } = await import('./feature');
  expect(mockedReadFeatureFlag()).toBe('mocked');
});

Mock CommonJS modules

If a dependency is loaded through require(), you can use rstest.mockRequire() or rstest.doMockRequire().

Use rstest.mockRequire()

rstest.mockRequire() is hoisted to the top of the file. It is useful for file-level mocking of CommonJS modules.

math.test.cjs
const { expect, rstest, test } = require('@rstest/core');
const { sum } = require('./math.cjs');

rstest.mockRequire('./math.cjs', () => ({
  sum: (a, b) => a + b + 100,
}));

test('mocks a CommonJS module loaded with require', () => {
  expect(sum(1, 2)).toBe(103);
});

Use rstest.doMockRequire()

Note that rstest.doMockRequire() is not hoisted and only affects later require() calls.

Note that this distinction matters when a package exposes both ESM and CommonJS entries. Mocking the ESM entry does not automatically affect the CommonJS entry, and vice versa.

Auto-mock modules

If you want to replace the module's function exports with mock functions first and then configure only selected exports in the test, you can use { mock: true }.

math.test.ts
import { expect, rstest, test } from '@rstest/core';
import { add } from './math';

rstest.mock('./math', { mock: true });

test('overrides one export', () => {
  rstest.mocked(add).mockReturnValue(100);
  expect(add(1, 2)).toBe(100);
});

Spy on a whole module

If you want to keep the real implementation and still assert calls, you can use { spy: true }.

calculator.test.ts
import { expect, rstest, test } from '@rstest/core';
import { add, calculate } from './calculator';

rstest.mock('./calculator', { spy: true });

test('tracks the internal call to add', () => {
  expect(calculate(1, 2)).toBe(3);
  expect(add).toHaveBeenCalledWith(1, 2);
});

Partially mock modules

If you want one export to keep the real implementation and another export to be replaced, you can use importActual.

date-utils.test.ts
import { expect, rstest, test } from '@rstest/core';
import * as actualDateUtils from './date-utils' with { rstest: 'importActual' };
import { formatDate, parseDate } from './date-utils';

rstest.mock('./date-utils', () => ({
  ...actualDateUtils,
  formatDate: rstest.fn().mockReturnValue('2026-03-19'),
}));

test('keeps parseDate real', () => {
  expect(formatDate(new Date())).toBe('2026-03-19');
  expect(parseDate('2026-03-19')).toBeInstanceOf(Date);
});

Note that factory functions are hoisted, so they should not read values that are initialized later in the same file.

Reuse manual mocks from __mocks__

If multiple tests reuse the same fake implementation, you can place it in __mocks__ and load it without passing a factory.

src/
  api.ts
  __mocks__/
    api.ts
tests/
  user-service.test.ts
user-service.test.ts
import { rstest } from '@rstest/core';

rstest.mock('../src/api');

Reset module state

If you want later import or require() calls to return the original module again, you can use these APIs:

Note that rstest.resetModules() does not cancel module mocking. To cancel module mocking, use rstest.unmock() or rstest.doUnmock().

For the full API and more examples, see Mock modules.

Mock functions

If a dependency is passed in as a callback or injected implementation, you can use rstest.fn() to create a mock function.

user.test.ts
import { expect, rstest, test } from '@rstest/core';

test('passes the selected id to the callback', () => {
  const onSelect = rstest.fn();

  onSelect('user-1');

  expect(onSelect).toHaveBeenCalledTimes(1);
  expect(onSelect).toHaveBeenCalledWith('user-1');
});

You can also override behavior through mock instance methods, for example by returning a different value for one call:

const fetchUser = rstest.fn(async (id: string) => ({ id, role: 'guest' }));

fetchUser.mockResolvedValueOnce({ id: '1', role: 'admin' });

For the full API and more examples, see Mock functions and MockInstance.

Spy on existing methods

If you want to keep the real object and still track calls or temporarily override behavior, you can use rstest.spyOn().

logger.test.ts
import { expect, rstest, test } from '@rstest/core';

test('logs a warning when validation fails', () => {
  const warn = rstest
    .spyOn(console, 'warn')
    .mockImplementation(() => undefined);

  console.warn('invalid payload');

  expect(warn).toHaveBeenCalledWith('invalid payload');
  warn.mockRestore();
});

This pattern is commonly used with globals such as console and Date, as well as shared objects that already exist in the test.

Deep-mock objects

If a dependency already exists in memory and you want to convert nested methods into mocks, you can use rstest.mockObject().

service.test.ts
import { expect, rstest, test } from '@rstest/core';

test('mocks nested methods', async () => {
  const service = rstest.mockObject({
    user: {
      fetch: async (id: string) => ({ id, name: 'real' }),
    },
    version: 'v1',
  });

  service.user.fetch.mockResolvedValue({ id: '1', name: 'mocked' });

  expect(service.version).toBe('v1');
  await expect(service.user.fetch('1')).resolves.toEqual({
    id: '1',
    name: 'mocked',
  });
});

If you want to keep the original nested implementations while still recording calls, you can pass { spy: true }.

For the full API and more examples, see Mock functions and MockInstance.

Clear mock state

If you need to clear call history or reset mock implementations, you can use these APIs:

  • clearMocks: clear call history before each test.
  • resetMocks: clear call history and reset mock implementations.
  • restoreMocks: restore spied descriptors on real objects.

For manual cleanup, the corresponding APIs are rstest.clearAllMocks(), rstest.resetAllMocks(), and rstest.restoreAllMocks().

Further reading