In this post I'm going to cover all the main things that you need to do when writing tests in Jest.
Writing Test Suites and Test Cases
All your assertions should go in a test()
function (or the alias it()
):
test('should add two numbers', () => {
expect(1 + 2).toBe(3);
});
// Using it (alias for test)
it('should multiply two numbers', () => {
expect(2 * 3).toBe(6);
});
You can group them with (nested) describe()
blocks:
describe('Calculator', () => {
describe('addition', () => {
it('should add positive numbers', () => {
expect(1 + 2).toBe(3);
});
it('should add negative numbers', () => {
expect(-1 + -2).toBe(-3);
});
});
});
Setup and Teardown - Running Code Before and After Tests
Use beforeAll()
, beforeEach()
, afterEach()
and afterAll()
to run code before/after tests.
If used inside a describe()
block, they run before/after each/all tests within that block.
beforeAll(() => {
// .. some code to run before any test runs
});
beforeEach(() => {
// .. some code to run before each test runs
});
afterEach(() => {
// .. some code to run after each test runs
});
afterAll(() => {
// .. some code to run after all test runs
});
Matchers - common matchers
Basic equality checks with .toBe()
:
expect(2 + 2).toBe(4);
Numbers and string comparisons:
// Truthiness
expect('Hello').toBeTruthy();
expect('').toBeFalsy();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('Hello').toBeDefined();
// Numbers
expect(2 + 2).toBeGreaterThan(3);
expect(2 + 2).toBeGreaterThanOrEqual(4);
expect(2 + 2).toBeLessThan(5);
expect(2 + 2).toBeLessThanOrEqual(4);
expect(0.1 + 0.2).toBeCloseTo(0.3);
// Strings
expect('team').not.toMatch(/I/);
expect('Christoph').toMatch(/stop/);
expect('Hello world').toEqual(
expect.stringContaining('world')
);
expect('Hello world').toContain(
'world'
);
Use toEqual()
/toStrictEqual()
for objects:
- toEqual() performs deep equality and treats missing vs undefined properties as equivalent. It does not ignore extra properties; for partial matches use
toMatchObject()
orexpect.objectContaining()
. - toStrictEqual() is like
toEqual()
but also fails when a property is missing vs set toundefined
, and it checks prototypes/types more strictly.
it('toEqual vs toStrictEqual demo', () => {
const nameAge = {
name: 'Bob',
age: 25,
};
const nameAge2 = {
name: 'Bob',
age: 25,
};
const nameAgeCity = {
name: 'Bob',
age: 25,
city: undefined,
};
// Both pass - toEqual treats missing and undefined properties as equivalent
expect(nameAge).toEqual(nameAge2);
expect(nameAge).toStrictEqual(
nameAge2
);
expect(nameAge).toEqual(nameAgeCity);
expect(nameAgeCity).toEqual(nameAge);
// toEqual passes, but toStrictEqual will fail when comparing to nameAgeCity
// expect(nameAge).toStrictEqual(nameAgeCity); // ❌ fails!
// expect(nameAgeCity).toStrictEqual(nameAge); // ❌ fails!
});
You can use toEqual()
for deep equality checks. Use toStrictEqual()
when objects must be exactly identical.
For partial matches, you can use toMatchObject()
or expect.objectContaining()
.
Partial object matches
Object matching with toMatchObject()
:
const user = {
id: 1,
name: 'Bob',
age: 30,
hobbies: ['reading', 'gaming'],
};
expect(user).toMatchObject({
name: 'Bob',
age: 30,
}); // Passes - user contains these properties with matching values
Use expect.objectContaining()
for partial matching within other matchers.
// Useful when the object is nested or part of a larger assertion
expect({
user: {
id: 1,
name: 'Bob',
age: 30,
city: 'New York',
},
timestamp: '2023-01-01',
}).toEqual({
user: expect.objectContaining({
name: 'Bob',
age: 30,
}),
timestamp: expect.any(String),
});
// Array containing objects
const users = [
{ id: 1, name: 'Bob', role: 'admin' },
{ id: 2, name: 'Bart', role: 'user' },
];
expect(users).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Bob',
}),
expect.objectContaining({
role: 'user',
}),
])
);
Use expect.arrayContaining()
for partial array matching (ordering doesn't matter)
expect([
'apple',
'banana',
'orange',
]).toEqual(
expect.arrayContaining([
'banana',
'apple',
])
);
Use expect.any()
for type checking:
expect([
1,
'hello, world',
true,
true,
]).toEqual([
expect.any(Number),
expect.any(String),
expect.any(Boolean),
expect.anything(),
]);
Exceptions
expect(() => {
throw new Error('Wrong!');
}).toThrow();
// checking error message:
expect(() => {
throw new Error('Wrong!');
}).toThrow('Wrong!');
// checking error type that was thrown
expect(() => {
throw new Error('Wrong!');
}).toThrow(Error);
Asynchronous Testing
Promises
test('fetches user data async', async () => {
const data = await someFnToGetData();
expect(data.name).toBe('some value');
});
test('resolves to user', async () => {
await expect(
someFnToGetData()
).resolves.toEqual({
name: 'some name here',
});
});
test('rejects with error', async () => {
await expect(
someFnThatRejects()
).rejects.toThrow('data not found');
});
Mocking
Mock functions
Use jest.fn()
to create a mock function, where you can check how many times it was called and with what arguments.
const mockFn = jest.fn();
mockFn('arg1', 'arg2');
mockFn.mockReturnValue(42);
mockFn
.mockReturnValueOnce(10)
.mockReturnValueOnce(20);
// Mock implementation
const mockCallback = jest.fn(
x => x * 2
);
expect(mockCallback(5)).toBe(10);
mockFn.mockResolvedValue(
'async result'
);
mockFn.mockResolvedValueOnce(
'first async'
);
mockFn.mockRejectedValue(
new Error('Async error')
);
Check how many times and what arguments were called with it:
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(
'arg1',
'arg2'
);
expect(mockFn).toHaveBeenLastCalledWith(
'arg1',
'arg2'
);
expect(mockFn).toHaveBeenNthCalledWith(
1,
'arg1',
'arg2'
);
Clear / reset the call history:
const mockFn = jest.fn();
mockFn.mockClear(); // Clear call history
mockFn.mockReset(); // Clear call history and implementations
jest.spyOn
const video = {
play() {
return true;
},
};
const spy = jest.spyOn(video, 'play');
video.play();
expect(spy).toHaveBeenCalled();
spy.mockRestore(); // Restore original implementation (for spies only)
Module Mocking
Automatic module mocking by passing a module name or path - everything will be a function that returns undefined
, or any exported consts will be set to undefined
:
You can easily get automatic module mocking. Exported functions become jest.fn()
(returning undefined
by default) and non-function exports become undefined
unless you provide a factory.
jest.mock('./utils');
You can also define the mock value, by passing in a factory function that returns the mocked import
jest.mock('./config', () => ({
apiUrl: 'http://localhost',
timeout: 5000,
}));
Use jest.requireActual()
in the mock factory to get the real implementation, so you can mock just some of the exports (in this example, only mocking add
).
jest.mock('./math', () => ({
...jest.requireActual('./math'),
add: jest.fn(() => 42),
}));
Timers/Fake Timers
Use fake timers in Jest so you can set fake system time, advance timers (without waiting in real time)
jest.useFakeTimers();
test('calls callback after 100 seconds', () => {
const callback = jest.fn();
setTimeout(callback, 100_000); // timeout in ms
// Fast-forward time in the test
jest.advanceTimersByTime(100_000);
expect(
callback
).toHaveBeenCalledTimes(1);
});
Running timers:
// Run all timers
jest.runAllTimers();
// Run only pending timers
jest.runOnlyPendingTimers();
// Run timers to next timer
jest.advanceTimersToNextTimer();
// Clear all timers
jest.clearAllTimers();
// Use real timers
jest.useRealTimers();
// Get current fake time
jest.now();
jest.getRealSystemTime();
Snapshot testing
First time you run code with snapshots, it will either create a new file with the snapshot data (with .toMatchSnapshot()
), or update the code inline (toMatchInlineSnapshot()
)
Update snapshots: jest -u
or jest --updateSnapshot
In the example below it is shown with the container from render()
, but you can pass most things to these snapshot functions (numbers, strings, arrays, objects, etc).
test('renders correctly', () => {
const { container } = render(
<SomeButtonComponent />
);
expect(container).toMatchSnapshot(); // << stored in another file
});
test('renders correctly inline', () => {
const { container } = render(
<SomeButtonComponent />
);
// first time run it with this in your code:
// expect(container).toMatchInlineSnapshot()
// and it will be updated to this:
expect(container)
.toMatchInlineSnapshot(`
<button
className="btn"
>
Click me
</button>
`);
});
Skipping, todo, and only
Use the modifiers like .skip
, .todo
, .only
to select what tests run:
// Run only this test
test.only('only this test will run', () => {
expect(true).toBe(true);
});
// Skip this test
test.skip('this test will be skipped', () => {
expect(true).toBe(false);
});
// Todo tests (placeholder)
test.todo('write this test later');
Using each()
Iterate over data for tests, using each()
.
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 1, expected: 3 },
])(
'add($a, $b) returns $expected',
({ a, b, expected }) => {
expect(a + b).toBe(expected);
}
);
You can also use jest.each()
with the template literal syntax:
// Using test.each with template literals
test.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`(
'add($a, $b) returns $expected',
({ a, b, expected }) => {
expect(a + b).toBe(expected);
}
);
Testing Errors
class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
}
}
test('throws custom error', () => {
expect(() => {
throw new CustomError(
'Something went wrong'
);
}).toThrow(CustomError);
});
Want to learn more about how to write Jest tests?
If you want to learn more about Jest (and Vitest), check out my free lessons on Jest fundamentals.