skip to content
Far World Labs

Clearing Mocks in Vitest

/

Vitest is a popular testing framework for Node and frontend JavaScript applications. In this post I’ll describe an effective approach for managing mocks as they’re shaped and reshaped from test to test.

In Vitest, mocks are created with vi.spyOn and vi.mock. vi.spyOn is the one I use most often. For the purposes of this post, I’ll stick with vi.spyOn, but the restoring logic works the same way in both.

Clearing, Resetting, and Restoring

vi.clearAllMocks, vi.resetAllMocks, and vi.restoreAllMocks are the functions used to broadly change the state of mocks in a test suite.

Let’s explore the situations you might need them.

Never Reset Mocks

resetAllMocks replaces the mock implementations with undefined functions, and zeroes out any invocation state.

resetAllMocks is a pitfall you might encounter when writing Vitest tests because there’s rarely a need to replace mock implementations with undefined functions. I consider it a pitfall because the naming suggests something useful, but it breaks tests that rely on some kind of mocking implementation. It’s confusing that a function named reset doesn’t reset state in a more intuitive way. There may be niche debugging scenarios where this is useful. I’m not sure. Leaving the mock implementation as-is, as clearAllMocks does, is normally a much better approach when resetting state between tests.

Rarely Restore Mocks

restoreAllMocks restores the original unmocked implementations, and zeroes out any invocation state.

Vitest mocks remain unchanged between tests and between describe blocks in the same test file, but they are restored to original implementations between test files. This behavior has the nice property that we can build up whatever complexity of mocking we need inside of a test file and modify parts of it along the way. restoreAllMocks has the effect of removing all mocking. You rarely want to undo mocking in a wholesale way, you usually only want to change some of it.

In some cases it’s useful to build all of your mocks from scratch–maybe a test file is making broad changes to mocks from test to test, or maybe you wish to have idempotent mocking that’s expensive to construct but easy to reason about. For that reason, restoreAllMocks should only occasionally be used.

Frequently Clear Mocks

clearAllMocks leaves mock implementations as-is, and zeroes out any invocation state.

I typically set up spies in beforeEach or in the test themselves. The mock state is cleared between tests with clearAllMocks and the mock implementations remained mocked for the next tests. This is the way.

Here’s an example of what’s usually needed:

beforeEach(async () => {
// start each test with cleared state
vi.clearAllMocks();
vi.spyOn(axios, 'create').mockReturnValue(vi.fn());
vi.spyOn(logService, 'info').mockReturnValue(vi.fn());
// ...
vi.spyOn(producerService, 'getInstance')
.mockImplementation(async () => {
return {
connect: vi.fn(() => Promise.resolve()),
send: vi.fn(() => Promise.resolve()),
on: vi.fn(),
};
});
});
// Tests

Notes on vi.mock

The problem with vi.mock is that it hoists its implementation at runtime, so it’s usually not possible to supply local or imported values. For that reason, I tend to avoid it.

vi.mock mocks the entire module, so I’ll still use it in certain cases. For example, constant variables are common in projects, so if you need to replace an exported constant value for a test, you can use the following pattern.

import * as constants from './constants/index.js';
// Make the constants mutable
vi.mock('./constants/index.js', async (importOriginal) => ({
...(await importOriginal()),
}));
//.. you can modify those values now
constants.foo = 'bar';

Conclusion

This is the main pattern I’ve settled on for clearing mocks for integration and unit tests in NodeJS a frontend systems. Vitest mostly has feature parity with Jest, so most of this should be true of Jest as well.

Testing is a complex area of software development, and much ink has been spilled on how to properly manage test doubles. Hopefully this post demystifies this one aspect!