1. upgrade to jest 28

The main dependencies and versions of jest in package.json after the upgrade are as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
"devDependencies": {
        "@jest/types": "^28.1.1",
        "@types/jest": "^28.1.1",
        "jest": "^28.1.1",
        "jest-environment-jsdom": "^28.1.1",
        "jest-extended": "^2.0.0",
        "ts-jest": "^28.0.5",
    }
}

Compared to the dependencies associated with jest@27, you need to add the jest-environment-jsdom library dependency separately if you need a jsdom execution environment, in addition to the updated version.

2. jest 28 error problems and solution reference

2.1 SyntaxError: Unexpected token ’export'

The main error message reference is as follows.

1
2
3
4
5
6
node_modules/uuid/dist/esm-browser/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){export { default as v1 } from './v1.js';
 
SyntaxError: Unexpected token 'export'
 
> 1 | import { v4 } from "uuid";

chalk, uuid and other libraries that are adapted to the latest esm scheme basically report this error. The main reason is that jest uses jsdom to execute with the browser-esm version of the default export, and the default transform of jest 28 filters the contents of the node_modules directory.

The current workaround can be as follows.

Method 1: Specify the entry for the commonjs version of the corresponding package in the moduleNameMapper configuration. Example.

1
2
3
4
5
6
7
8
9
/** @type {import('@jest/types').Config.InitialOptions } */
module.exports = {
    testEnvironment: 'jsdom',
    moduleNameMapper: {
        '^uuid$': require.resolve('uuid'),
        // 或直接指定入口 js 路径
        // '^uuid$': '<rootDir>/node_modules/uuid/dist/index.js',
    }
}

Method 2: Modify the jest transform filtering rules to allow transformations to be performed for these unusual dependencies. example.

1
2
3
4
module.exports = {
  testEnvironment: 'jsdom',
  transformIgnorePatterns: ['/node_modules/(?!(uuid|xxx)/)'],
}

Hint: Method 2 uses browser scheme, uuid library will also report an error because the current version of jsdom does not support crypto.getRandomValues.

Option 3: mock unimportant dependency libraries. If the dependency is not heavily used and has no impact on the test logic, you can just mock it. For example, mock for uuid.

1
2
3
4
5
jest.mock('uuid', () => ({
    v1: () => '110ec58a-a0f2-4ac4-8393-c866d813b8d1',
    v4: () => '110ec58a-a0f2-4ac4-8393-c866d813b8d1',
    v5: () => '110ec58a-a0f2-4ac4-8393-c866d813b8d1',
});

2.2 setTimeout: Matcher error: received value must be a mock or spy function

Many use cases involving setTimeout calls report errors as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Received has value: [Function setTimeout]
 
 expect(received).toHaveBeenCalledTimes(expected)
 
    Matcher error: received value must be a mock or spy function
 
    Received has type:  function
    Received has value: [Function setTimeout]
 
      111 |         jest.runAllTimers();
      112 |         // Number of timer executions
    > 113 |         expect(setTimeout).toHaveBeenCalledTimes(1);

A look at the test code reveals a ts type exception. The original writeup was

1
jest.useFakeTimers('legacy');

Just change the incoming parameters to the following.

1
jest.useFakeTimers({ legacyFakeTimers: true });