Skip to main content

Jest-uatlity: Making work with Jest fun again

· 4 min read
Abdeldjalil Fortas

Jest is my go-to testing framework, it's the default in many of tools I use at work in my side projects and I didn't bother to reconfigure NestJS or NX.dev before I start a new project. Jest works for me, and works well.

But sometimes... Sometimes... I have to Google a little bit harder before I figure out how to do things.

In this blog I will share some of the tricks I learned, and some of the concepts I didn't know which led me to a frustrating debugging sessions. For example, the fact that jest.mock is hoisted, which makes using variables in mocked modules a bit tricky.

This blog will be updated each time I remember some of the tricks, or I learn something new about Jest.

Snippets

Mock promise rejection

Source:

it('tests error with async/await and rejects', async () => {
expect.assertions(1);
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

Mocked methods chaining

Example:

if (errors.length) return res.status(400).json(errors);

Mock:

mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};

Ref: https://stackoverflow.com/a/75811953/5078746

IDE Setup

VS Code

Tricks

Using it.each and describe.each

Example:

  it.each([
["no args were passed", undefined, productInfoMissingErr],
["no product IDs were passed", noIdData, productInfoMissingErr],
[
"an empty array of products was passed",
{ products: [] },
productIdMissingErr,
],
])("should throw if %s", (caseTitle, input, errMsg) => {
return readPricing().catch((e: Error) => {
expect.assertions(3);
expect(<jest.Mock>fetch).not.toHaveBeenCalled();
expect(e).toBeInstanceOf(TypeError);
expect(e.message).toBe(errMsg);
});
});
tip

If you are using jest-snippets in VS Code, just type ite and desce.

Using jest-when to specify dynamic returns matched with specific arguments

Package:

Installation

npm i jest-when && npm i -D @types/jest-when

Example

const fn = jest.fn()                   
when(fn)
.calledWith(/* any matchers here */)
.mockReturnValue(/* some value */)

Using jest-extended to have more matchers

Package:

Installation

npm i -D jest-extended

Example with .toBeAfter(date) matcher:

test('passes when input is after date', () => {
expect(new Date('01/01/2019')).toBeAfter(new Date('01/01/2018'));
expect('01/01/2018').not.toBeAfter(new Date('01/01/2019'));
});

Learnings

Why does Jest run faster with --maxWorkers=50%?

Ref: https://stackoverflow.com/a/75490452/5078746

That cost of spawning and scheduling multiple jest-workers might be sometimes greater than the gains we may get with parallelization. By using multiple workers you are instantiating more objects that will load different files from the disk. That's why in small and atomic tests you don't see any (or just a small) performance gain by using --maxWorkers=50%|1.

Globals in Jest environment differ from NodeJS's

Example #1:

it("throws TypeError when url is invalid", () => {
expect(() => {
new URL(""); // This does not work
// throw new TypeError(); // This works
}).toThrow(TypeError);
})

Proposed solution: Throw custom errors


Answers on StackOverflow:

jest.mock calls are automatically hoisted

Resources:

Jest mocks have to have mock in names

Mocks must have mock prefix in the name, otherwise you will see the following error:

    Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` (case insensitive) are permitted.

For example here:

const fakeEmoji = jest.fn();

jest.mock("./emoji", () => ({
getEmoji: fakeEmoji,
}));

const { getHello } = require("./hello");

describe("Testing my-module", () => {
beforeEach(() => {
fakeEmoji.mockReturnValue("😏");
});

it('should say "Hello {NAME}" with a smirk', () => {
expect(getHello("Kadour")).toBe("Hello Kadour 😏");
});
});

My mock function (fakeEmoji) must have mock prefix.