How to replace @types/jest with @jest/globals and jest-mock
Kengo TODA
Posted on August 22, 2021
I believe that it's always better to prefer official type definitions rather than unofficial DefinitelyTyped. And Jest v27 introduced new defaults so I thought that it's a nice timing to replace @types/jest
with @jest/globals
and jest-mock
. I'll share several problems I met:
Dependency Update
jest-circus
is now default test runner, so we don't need to depend on it directly. Also remove @types/jest
to replace with @jest/globals
and jest-mock
:
npm remove jest-circus @types/jest
npm add -D jest@^27.0.6 ts-jest@^27.0.5
Configuration
I'm using ts-jest
to transpile TypeScript code, and its configuration is like below. Now we can remove both testEnvironment
and testRunner
in most cases:
const { defaults: tsjPreset } = require('ts-jest/presets')
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
// no need `testEnvironment: "node"` any more
testMatch: ['**/*.test.ts'],
// no need `testRunner: "jest-circus"` any more
transform: {
...tsjPreset.transform,
},
verbose: true
}
I do not use preset to configure ts-jest
, to let other integrations like puppeteer use it.
Import from @jest/globals
and jest-mock
As described in the official API Document, we can import Jest API from @jest/globals
module.
About SpyInstance
, @types/jest
provides it as jest.SpyInstance
. But official type definition provides it as import { SpyInstance } from 'jest-mock';
... with generics!
import { beforeEach, jest } from '@jest/globals';
import { SpyInstance } from 'jest-mock';
let spyOSHomedir: SpyInstance<string, []>;
beforeEach(() => {
spyOSHomedir = jest.spyOn(os, 'homedir');
spyOSHomedir.mockReturnValue(__dirname);
});
This generics will bring better type safety, and tons of code change. For instance, mocked function sometimes wants to return the value that satisfies required type partially. In the following case, mocked fs.statSync
returns an object that has only isFile()
method even though the required type fs.Stats
has more properties and methods. In such case we can use the Partial utility type:
import { beforeEach, jest } from '@jest/globals';
import { SpyInstance } from 'jest-mock';
import fs from 'fs';
let spyFsStat: SpyInstance<Partial<fs.Stats | fs.BigIntStats>, [fs.PathLike, fs.StatOptions?]>
beforeEach(() => {
spyFsStat = jest.spyOn(fs, 'statSync');
spyFsStat.mockImplementation((file: fs.PathLike) => {
return { isFile: () => file === expectedJdkFile };
});
});
One more example: when you mock overloaded methods, you'll face some difficulty like:
https://javascript.plainenglish.io/mocking-ts-method-overloads-with-jest-e9c3d3f1ce0c
By applying the solution introduced in that article, we may lose integrity in test codes. So it would be an acceptable solution to use unknown
in some cases:
let spyFsReadDir: SpyInstance<unknown, Parameters<typeof fs.readdirSync>>;
beforeEach(() => {
// returned value is typed as `Dirent[]` but we want to return `string[]`, so use `unknown` to relax mismatch
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['JavaTest']);
});
That's all, you may refer to my commit replacing DefinitelyTyped with official type definitions. Thank you!
Posted on August 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.