我如何使用转译器设置 Jest,以便我可以自动模拟 DOM 和 canvas?
How can I setup Jest with a transpiler so that I can have automatically mocked DOM and canvas?
我有一个基于浏览器的小型游戏,我正在尝试启动 Jest 和 运行。
我的目标是能够编写测试,并将它们 运行 与 Jest 一起使用,而不是有任何额外的 DOM- 或浏览器 API- 相关的错误消息.
由于游戏使用了 DOM 和 canvas,我需要一个解决方案,我可以手动模拟它们,或者让 Jest 为我处理。至少,我想验证 'data model' 和我的逻辑是否正常。
我也在使用 ES6 模块。
这是我到目前为止尝试过的方法:
- 试过运行笑话:
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/home/dingo/code/game-sscce/game.spec.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { Game } from './game';
^^^^^^
SyntaxError: Cannot use import statement outside a module
这里明白了,可以实验性的开启ES模块支持,或者使用转译器输出Jest可以识别的ES5,运行.
所以我的选择是:
- 启用实验性 ES 模块支持
- 使用 Babel 转译
- 使用 Parcel 转储
- 使用 Webpack 转译
我决定尝试 Babel 并在此处查看说明:https://jestjs.io/docs/en/getting-started#using-babel
- 我在根目录下创建了一个babel.config.js文件。
安装 babel 并创建配置文件后,这是一个 SSCCE:
babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env'
]
],
};
game.js
export class Game {
constructor() {
document.getElementById('gameCanvas').width = 600;
}
}
new Game();
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
expect(new Game()).toBeDefined();
});
index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script type="module" src="game.js" defer></script>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas" />
</div>
</body>
</html>
package.json
{
"name": "game-sscce",
"version": "1.0.0",
"scripts": {
"test": "jest"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/preset-env": "^7.12.13",
"babel-jest": "^26.6.3",
"jest": "^26.6.3"
}
}
现在,当我再次尝试 运行ning Jest 时,我得到:
FAIL ./game.spec.js
● Test suite failed to run
TypeError: Cannot set property 'width' of null
1 | export class Game {
2 | constructor() {
> 3 | document.getElementById('gameCanvas').width = 600;
| ^
4 | }
5 | }
6 |
at new Game (game.js:3:5)
at Object.<anonymous> (game.js:7:1)
at Object.<anonymous> (game.spec.js:1:1)
...现在,我不知道该怎么办。如果文件没有被识别,那么我怀疑 Jest 没有正确使用 jsdom。我应该配置其他东西吗?
调查:
Jest runs with jsdom by default.
document
实际存在:
但是,由于它是模拟的,getElementById()
只是 returns null
。
在这种情况下,无法 return HTML 文档中定义的现有 canvas。相反,可以通过编程方式创建 canvas:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
}
}
new Game();
然而,getElementById()
仍然会 return null
,因此必须模拟此调用:
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
jest.spyOn(document, 'getElementById').mockReturnValue({})
expect(new Game()).toBeDefined();
});
测试仍然失败运行:
FAIL ./game.spec.js
● Test suite failed to run
TypeError: Cannot read property 'append' of null
3 | const canvas = document.createElement('canvas');
4 | canvas.setAttribute('id', 'gameCanvas');
> 5 | document.getElementById('gameContainer').append(canvas);
| ^
6 |
7 | canvas.width = 600;
8 |
at new Game (game.js:5:5)
at Object.<anonymous> (game.js:16:1)
at Object.<anonymous> (game.spec.js:1:1)
这是因为 Game
由于最后一行的 new Game()
调用而在 Jest 导入它时立即实例化自身。一旦摆脱那个:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
}
}
我们得到:
FAIL ./game.spec.js
✕ instantiates Game (7 ms)
● instantiates Game
TypeError: document.getElementById(...).append is not a function
3 | const canvas = document.createElement('canvas');
4 | canvas.setAttribute('id', 'gameCanvas');
> 5 | document.getElementById('gameContainer').append(canvas);
| ^
6 |
7 | canvas.width = 600;
8 |
at new Game (game.js:5:46)
at Object.<anonymous> (game.spec.js:5:10)
更近一步,但 append()
调用也必须被模拟出来:
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
jest.spyOn(document, 'getElementById').mockReturnValue({
append: jest.fn().mockReturnValue({})
});
expect(new Game()).toBeDefined();
});
...现在测试通过了:
PASS ./game.spec.js
✓ instantiates Game (9 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
有趣的是,jsdom return 是一个 HTMLCanvasElement 以编程方式创建和模拟时:
然而,它并不能真正用于任何用途:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);
}
}
如失败测试所示:
FAIL ./game.spec.js
✕ instantiates Game (43 ms)
● instantiates Game
TypeError: Cannot set property 'fillStyle' of null
10 | var ctx = canvas.getContext('2d');
11 |
> 12 | ctx.fillStyle = 'rgb(200, 0, 0)';
| ^
13 | ctx.fillRect(10, 10, 50, 50);
14 |
15 | ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
at new Game (game.js:12:5)
at Object.<anonymous> (game.spec.js:7:10)
console.error
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:130:58)
at new Game (/home/dingo/code/game-sscce/game.js:10:22)
at Object.<anonymous> (/home/dingo/code/game-sscce/game.spec.js:7:10)
at Object.asyncJestTest (/home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:75:41 undefined
8 | canvas.width = 600;
9 |
> 10 | var ctx = canvas.getContext('2d');
| ^
11 |
12 | ctx.fillStyle = 'rgb(200, 0, 0)';
13 | ctx.fillRect(10, 10, 50, 50);
为了能够进一步测试,必须满足以下两个条件之一:
- canvas必须安装为jsdom的对等依赖,
- jest-canvas-mock 必须安装。
我有一个基于浏览器的小型游戏,我正在尝试启动 Jest 和 运行。
我的目标是能够编写测试,并将它们 运行 与 Jest 一起使用,而不是有任何额外的 DOM- 或浏览器 API- 相关的错误消息.
由于游戏使用了 DOM 和 canvas,我需要一个解决方案,我可以手动模拟它们,或者让 Jest 为我处理。至少,我想验证 'data model' 和我的逻辑是否正常。
我也在使用 ES6 模块。
这是我到目前为止尝试过的方法:
- 试过运行笑话:
Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/en/ecmascript-modules for how to enable it.
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/home/dingo/code/game-sscce/game.spec.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { Game } from './game';
^^^^^^
SyntaxError: Cannot use import statement outside a module
这里明白了,可以实验性的开启ES模块支持,或者使用转译器输出Jest可以识别的ES5,运行.
所以我的选择是:
- 启用实验性 ES 模块支持
- 使用 Babel 转译
- 使用 Parcel 转储
- 使用 Webpack 转译
我决定尝试 Babel 并在此处查看说明:https://jestjs.io/docs/en/getting-started#using-babel
- 我在根目录下创建了一个babel.config.js文件。
安装 babel 并创建配置文件后,这是一个 SSCCE:
babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env'
]
],
};
game.js
export class Game {
constructor() {
document.getElementById('gameCanvas').width = 600;
}
}
new Game();
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
expect(new Game()).toBeDefined();
});
index.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script type="module" src="game.js" defer></script>
</head>
<body>
<div id="gameContainer">
<canvas id="gameCanvas" />
</div>
</body>
</html>
package.json
{
"name": "game-sscce",
"version": "1.0.0",
"scripts": {
"test": "jest"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/preset-env": "^7.12.13",
"babel-jest": "^26.6.3",
"jest": "^26.6.3"
}
}
现在,当我再次尝试 运行ning Jest 时,我得到:
FAIL ./game.spec.js
● Test suite failed to run
TypeError: Cannot set property 'width' of null
1 | export class Game {
2 | constructor() {
> 3 | document.getElementById('gameCanvas').width = 600;
| ^
4 | }
5 | }
6 |
at new Game (game.js:3:5)
at Object.<anonymous> (game.js:7:1)
at Object.<anonymous> (game.spec.js:1:1)
...现在,我不知道该怎么办。如果文件没有被识别,那么我怀疑 Jest 没有正确使用 jsdom。我应该配置其他东西吗?
调查:
Jest runs with jsdom by default.
document
实际存在:
但是,由于它是模拟的,getElementById()
只是 returns null
。
在这种情况下,无法 return HTML 文档中定义的现有 canvas。相反,可以通过编程方式创建 canvas:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
}
}
new Game();
然而,getElementById()
仍然会 return null
,因此必须模拟此调用:
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
jest.spyOn(document, 'getElementById').mockReturnValue({})
expect(new Game()).toBeDefined();
});
测试仍然失败运行:
FAIL ./game.spec.js
● Test suite failed to run
TypeError: Cannot read property 'append' of null
3 | const canvas = document.createElement('canvas');
4 | canvas.setAttribute('id', 'gameCanvas');
> 5 | document.getElementById('gameContainer').append(canvas);
| ^
6 |
7 | canvas.width = 600;
8 |
at new Game (game.js:5:5)
at Object.<anonymous> (game.js:16:1)
at Object.<anonymous> (game.spec.js:1:1)
这是因为 Game
由于最后一行的 new Game()
调用而在 Jest 导入它时立即实例化自身。一旦摆脱那个:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
}
}
我们得到:
FAIL ./game.spec.js
✕ instantiates Game (7 ms)
● instantiates Game
TypeError: document.getElementById(...).append is not a function
3 | const canvas = document.createElement('canvas');
4 | canvas.setAttribute('id', 'gameCanvas');
> 5 | document.getElementById('gameContainer').append(canvas);
| ^
6 |
7 | canvas.width = 600;
8 |
at new Game (game.js:5:46)
at Object.<anonymous> (game.spec.js:5:10)
更近一步,但 append()
调用也必须被模拟出来:
game.spec.js
import { Game } from './game';
test('instantiates Game', () => {
jest.spyOn(document, 'getElementById').mockReturnValue({
append: jest.fn().mockReturnValue({})
});
expect(new Game()).toBeDefined();
});
...现在测试通过了:
PASS ./game.spec.js
✓ instantiates Game (9 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
有趣的是,jsdom return 是一个 HTMLCanvasElement 以编程方式创建和模拟时:
然而,它并不能真正用于任何用途:
game.js
export class Game {
constructor() {
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'gameCanvas');
document.getElementById('gameContainer').append(canvas);
canvas.width = 600;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);
}
}
如失败测试所示:
FAIL ./game.spec.js
✕ instantiates Game (43 ms)
● instantiates Game
TypeError: Cannot set property 'fillStyle' of null
10 | var ctx = canvas.getContext('2d');
11 |
> 12 | ctx.fillStyle = 'rgb(200, 0, 0)';
| ^
13 | ctx.fillRect(10, 10, 50, 50);
14 |
15 | ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
at new Game (game.js:12:5)
at Object.<anonymous> (game.spec.js:7:10)
console.error
Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
at module.exports (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
at HTMLCanvasElementImpl.getContext (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
at HTMLCanvasElement.getContext (/home/dingo/code/game-sscce/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:130:58)
at new Game (/home/dingo/code/game-sscce/game.js:10:22)
at Object.<anonymous> (/home/dingo/code/game-sscce/game.spec.js:7:10)
at Object.asyncJestTest (/home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /home/dingo/code/game-sscce/node_modules/jest-jasmine2/build/queueRunner.js:75:41 undefined
8 | canvas.width = 600;
9 |
> 10 | var ctx = canvas.getContext('2d');
| ^
11 |
12 | ctx.fillStyle = 'rgb(200, 0, 0)';
13 | ctx.fillRect(10, 10, 50, 50);
为了能够进一步测试,必须满足以下两个条件之一:
- canvas必须安装为jsdom的对等依赖,
- jest-canvas-mock 必须安装。