Recently, I started with a side project that uses TypeScript in the frontend and in the backend. I wanted to do things test-driven and chose the Jest framework as it is a very popular choice.
When using Jest with TypeScript, I encountered some struggles and pitfalls I ran into.
I would like to share the learnings I had π©βπ»πββοΈπ€·ββοΈπ€¦ββοΈπ©βπ€π.
Using Jest with TypeScript
In the first place, jest recommends to use TypeScript via Babel in their documentation.
I couldn't get Babel configured correctly, so it did not work for me. I used the alternative approach via ts-jest:
npm install --save-dev jest typescript ts-jest @types/jest
npx ts-jest config:init
It generates a jest.config.js
file with:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
If you are testing browser stuff, you can change testEnvironment to 'jsdom'
to get access to DOM APIs in your tests.
Mocking stuff in TypeScript
When I first tried to use mocks in TypeScript, I got a lot of type errors when trying to access properties from the mock (eg. mockClear()
).
I figured out ts-jest
provides a mocked()
wrapper function that adds all mock properties to the function or object you would like to mock.
Example:
Let's look at an example app that fetches a person from the Star Wars API
// api.ts
import fetch from 'node-fetch';
const BASE_URL = 'http://swapi.co/api/'
export async function getPerson(id: number) {
const response = await fetch(BASE_URL + `people/${id}/`);
const data = await response.json();
return data;
}
// index.ts
import { getPerson } from "./api";
async function main() {
const luke = await getPerson(1);
console.log(luke);
}
main();
The testing code mocks fetch
and provides a mock implementation for it:
// api.test.ts
import fetch from 'node-fetch';
import { mocked } from 'ts-jest/utils';
import { getPeople } from './api';
jest.mock('node-fetch', () => {
return jest.fn();
});
beforeEach(() => {
mocked(fetch).mockClear();
});
describe('getPeople test', () => {
test('getPeople should fetch a person', async () => {
// provide a mock implementation for the mocked fetch:
mocked(fetch).mockImplementation((): Promise<any> => {
return Promise.resolve({
json() {
return Promise.resolve({name: 'Luke Vader'});
}
});
});
// getPeople uses the mock implementation for fetch:
const person = await getPeople(1);
expect(mocked(fetch).mock.calls.length).toBe(1);
expect(person).toBeDefined();
expect(person.name).toBe('Luke Vader');
});
});
Ignore css/scss/less imports
By default, jest tries to parse css imports as JavaScript. In order to ignore all things css, some extra configuration is needed.
Add a stub-transformer to your project which returns css imports as empty objects:
module.exports = {
process() {
return 'module.exports = {};';
},
getCacheKey() {
return 'stub-transformer';
},
};
Add this to your jest.config.js
configuration file:
module.exports = {
// ...
transform: {
"\\.(css|less|scss)$": "./jest/stub-transformer.js"
}
};
node Express: mocking an express Response object
I was struggling with testing express routes on the backend side. I figured out that I don't need a complete implementation of the express response object. It's sufficient to implement just the properties you actually use. So, I came up with a minimal fake Partial<Response>
object, wrapped into a Fakexpress
class:
import { Response } from 'express';
export class Fakexpress {
constructor(req: any) {
this.req = req;
}
res : Partial<Response> = {
statusCode: 200,
status: jest.fn().mockImplementation((code) => {
this.res.statusCode = code;
return this.res;
}),
json: jest.fn().mockImplementation((param) => {
this.responseData = param;
return this.res;
}),
cookie: jest.fn(),
clearCookie: jest.fn()
}
req: any;
responseData: any;
}
The test code looks like this:
test('Testing an express route', async () => {
const xp = new Fakexpress({
params: {
name: 'max'
}
});
const searchResult = {
name: 'max',
fullName: 'Max Muster',
description: 'Full Stack TypeScript developer',
pronouns: 'they/them',
};
await expressRoute(xp.req as Request, xp.res as Response);
expect(xp.responseData).toStrictEqual(searchResult);
expect(xp.res.statusCode).toBe(200);
});
Top comments (9)
I ran into something in this category with mocking a module with
jest.mock()
.This alone is enough to mock
fetchResource
, but with TypeScript we still get the intellisense from the original function rather than the mock function. I just reassignedfetchResource
and typed the new function:Finally I had access to things like
fetchResource.mockResolvedValueOnce()
Cool, learned something new today, thank you =)
You just ended my hour long search for a solution. Many thanks!
TIL I learned:
Thanks for the tip! This is really helpful to avoid πing against Typescript when manipulating mocks.
That's probably a matter of taste, but I think the mock implementation could be simplified to something like this:
Thanks very much for this, I was struggling to mock node-fetch in TypeScript and this was very helpful :)
Awesome, glad it was helpful to you :)
I tried to mock mongoose.connect in my Nestjs, but failed, stackoverflow.com/questions/629087...
Is there a alternative of ts-jest/utils mocked, in the latest versions of ts-jest I do not see any support of mocked, let me know if there is any solution for this.
Thanks
Yes, the mocked test helper is actually in the main jest library now, as
jest.mocked()
. It seems to have been removed here: github.com/kulshekhar/ts-jest/pull...