Jest/Vitest Testing Fundamentals
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Getting Started
Common Matchers
Basic Testing Concepts
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Getting Started
Common Matchers
Basic Testing Concepts
Mock functions are a feature of Jest and Vitest that let you:
- track how many times it was called
- track what arguments it was called with
- easily provide mock return values
- or easily provide a mock implementation
Both Jest and Vitest provide built-in functions for creating mock functions:
- Jest: Use the
jest.fn()
method - Vitest: Use the
vi.fn()
method
In our test runner on this page, you can use either method.
Basic Example
Creating a mock function is straightforward - you simply call jest.fn()
or vi.fn()
:
// Using Jest
const mockFunction = jest.fn();
Or if using Vitest:
// Using Vitest
const mockFunction = vi.fn();
These mock functions can then be used in place of real functions in your tests, allowing you to track calls, arguments, and return values.
Checking Mock Function Calls (was it called? how many times?)
You might be thinking why bother? You could just pass around () => undefined
, right?
The main reason and purpose for using jest.fn()
/vi.fn()
is that you can track if the function was called, what it was called with, and it is easy to get it to return mock values.
Asserting if a mock function was called
const mockFunction = vi.fn(); // or jest.fn()
expect(
mockFunction
).not.toHaveBeenCalled();
// or you could do .toHaveBeenCalledTimes(0)
mockFunction(); // << call it once
expect(mockFunction).toHaveBeenCalled();
mockFunction(); // << call it again
// we can now check it was called 2 times
expect(
mockFunction
).toHaveBeenCalledTimes(2);
Asserting the arguments passed to a mock function
const mockFunction = vi.fn(); // or jest.fn()
mockFunction('arg1', 'arg2');
expect(
mockFunction
).toHaveBeenCalledWith('arg1', 'arg2');
You can also check when it was called with certain arguments
const mockFunction = vi.fn(); // or jest.fn()
const bart = { name: 'Bart' };
const lisa = { name: 'Lisa' };
mockFunction(bart);
mockFunction(lisa);
expect(
mockFunction
).toHaveBeenCalledTimes(2);
// check it was called with bart or lisa objects,
// without caring about when it was called:
expect(
mockFunction
).toHaveBeenCalledWith(bart);
expect(
mockFunction
).toHaveBeenCalledWith(lisa);
// but you can also check when it was called
expect(
mockFunction
).toHaveBeenLastCalledWith(lisa);
expect(
mockFunction
).toHaveBeenNthCalledWith(1, bart);
expect(
mockFunction
).toHaveBeenNthCalledWith(2, lisa);
mockFunction(
'some final call',
bart,
lisa
);
expect(
mockFunction
).toHaveBeenCalledTimes(3);
expect(
mockFunction
).toHaveBeenCalledWith(
'some final call',
bart,
lisa
);
Mock return values
Sometimes you will want to use a mock function, but have it return a specific value.
const mockFunction = vi.fn(); // or jest.fn()
// Make the mock function return a specific value
mockFunction.mockReturnValue(
'hello world'
);
expect(mockFunction()).toBe(
'hello world'
);
expect(
mockFunction
).toHaveBeenCalledTimes(1);
You can also make it return different values on consecutive calls using mockReturnValueOnce()
.
Once all the mockReturnValueOnce
values have been consumed, it will continue to return the value from mockReturnValue()
.
const mockCounter = vi
.fn()
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
.mockReturnValue(3);
console.log(mockCounter()); // 1
console.log(mockCounter()); // 2
console.log(mockCounter()); // 3
console.log(mockCounter()); // 3 (subsequent calls return 3)
Mocking async functions
Instead of mockReturnValue()
, you can use mockResolvedValue()
(or mockRejectedValue()
).
const mockFetch = vi.fn(); // or jest.fn()
// Mock a successful API response
mockFetch.mockResolvedValue({
data: 'user data',
});
// Using the mock in an async function
async function getUserData() {
const response = await mockFetch(
'/api/user'
);
return response;
}
// Test the async function
expect(await getUserData()).toEqual({
data: 'user data',
});
expect(mockFetch).toHaveBeenCalledWith(
'/api/user'
);
You can use a similar function, .mockRejectedValue()
, to simulate an error (a rejected promise):
mockFetch.mockRejectedValue(
new Error('Network error')
);
await expect(
getUserData()
).rejects.toThrow('Network error');
Mocking Function Implementation
Other times you will need to do some more complex return values than just mockReturnValue()
.
In that case you can provide a mock implementation for the function.
Here is how to do it in Vitest:
const mockFunction = vi.fn(
() => 'mocked value'
);
And in Jest (same syntax)
const mockFunction = jest.fn(
() => 'mocked value'
);
You can also do it like this:
const mockFunction = vi.fn(); // or jest.fn()
mockFunction.mockImplementation(
() => 'mocked value 2'
);
// You can also change the implementation later
mockFunction.mockImplementation(
(arg1, arg2) => {
return `Called with ${arg1} and ${arg2}`;
}
);
console.log(
mockFunction('hello', 'world')
); // 'Called with hello and world'
Clearing Mock Calls
Sometimes in your tests, you will want to clear or reset the internal state of the mock function.
There are a few related methods.
mockClear()
mockFunction.mockClear()
only clears the mock's call history (how many times it was called, what arguments were passed).
It does NOT reset the mock implementation or return values.
This is useful when you want to assert fresh calls in a new test while keeping the same mock behaviour.
You will often want to call mockClear()
in a beforeEach
if your mock function is shared between different tests.
const mockFn = vi.fn(
() => 'mocked value'
);
mockFn('arg1');
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(
'arg1'
);
mockFn.mockClear();
expect(mockFn).toHaveBeenCalledTimes(0); // call history cleared
mockFn(); // still returns 'mocked value'
mockReset()
mockFunction.mockReset()
completely resets the mock function to its initial state, clears all mock history AND removes any mock implementation. It is as if you just created it with vi.fn()
(or jest.fn()
).
This clears all mock history (calls, arguments) AND removes any mock implementation or return values.
After calling mockReset()
, the mock function will return undefined
when called.
const mockFn = vi.fn(() => 'original');
mockFn.mockReturnValue('mocked');
mockFn(); // returns 'mocked'
expect(mockFn).toHaveBeenCalledTimes(1);
mockFn.mockReset();
mockFn(); // returns undefined
expect(mockFn).toHaveBeenCalledTimes(1); // only counts calls after reset
mockRestore()
And if it is a spy (see an upcoming lesson), then you can do mockFunction.mockRestore()
to restore its original implementation
Clearing/Restoring/Resetting all mocks
If you have lots of mock functions, it can mean you have tons of someFn.mockClear()
to call.
If you want to just clear all mock functions, this is built into Jest and Vitest with vi.clearAllMocks()
/ vi.resetAllMocks()
/ vi.restoreAllMocks()
(or jest.clearAllMocks()
etc).
describe('clearing all mocks example', () => {
const mockFn1 = vi.fn();
const mockFn2 = vi.fn();
const mockFn3 = vi.fn();
beforeEach(() => {
// Clear all mocks before each test
vi.clearAllMocks();
});
test('first test', () => {
mockFn1('test1');
mockFn2('test1');
mockFn3('test1');
expect(
mockFn1
).toHaveBeenCalledTimes(1);
expect(
mockFn2
).toHaveBeenCalledTimes(1);
expect(
mockFn3
).toHaveBeenCalledTimes(1);
});
test('second test - all mocks are cleared', () => {
// All mocks start fresh thanks to clearAllMocks in beforeEach
expect(
mockFn1
).toHaveBeenCalledTimes(0);
expect(
mockFn2
).toHaveBeenCalledTimes(0);
expect(
mockFn3
).toHaveBeenCalledTimes(0);
mockFn1('test2');
expect(
mockFn1
).toHaveBeenCalledTimes(1);
expect(
mockFn1
).toHaveBeenCalledWith('test2');
});
});
Lesson Task
There are a bunch of incomplete tests.
For the clicker
tests, it is set up to call the clicker()
function which results in the mock function being called. You should assert that it was called.
.toHaveBeenCalledTimes(1)
.toHaveBeenCalledWith('some-argument')
For the logger
tests, you need to:
- check it was called expected number of times (similar to the clicker tests)
- Then we have some tests where we call
.toHaveBeenCalledTimes(0)
. So we need to set the internal count of calls back to 0 usingmockFn.mockClear()
- Lastly at the end of the file we have an assertion that
expect(someFn('whatever')).toBe('hello')
. We can control whatsomeFn
returns with.mockReturnValue(...)
. You will also need to fix the assertion on number of calls too.
Ready to try running tests for this lesson?