如何在 Jest 中为我的测试添加 <canvas> 支持?

How to add <canvas> support to my tests in Jest?

在我的Jest unit test I am rendering a component with a ColorPickerColorPicker 组件创建一个 canvas 对象和 2d 上下文,但是 returns 'undefined' 会引发错误 "Cannot set property 'fillStyle' of undefined"

if (typeof document == 'undefined') return null; // Dont Render On Server
var canvas = document.createElement('canvas'); 
canvas.width = canvas.height = size * 2;
var ctx = canvas.getContext('2d'); // returns 'undefined'
ctx.fillStyle = c1; // "Cannot set property 'fillStyle' of undefined"

我无法弄清楚为什么无法获得 2d 上下文。也许我的测试配置有问题?

"jest": {
  "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
  "unmockedModulePathPatterns": [
    "<rootDir>/node_modules/react",
    "<rootDir>/node_modules/react-dom",
    "<rootDir>/node_modules/react-addons-test-utils",
    "<rootDir>/node_modules/react-tools"
  ],
  "moduleFileExtensions": [
    "jsx",
    "js",
    "json",
    "es6"
  ],
  "testFileExtensions": [
    "jsx"
  ],
  "collectCoverage": true
}

这是因为您的测试未在真实浏览器中 运行。 Jest 使用 jsdom 模拟 DOM 的必要部分,以便能够 运行 Node 中的测试,从而避免了浏览器通常会进行的样式计算和渲染。这很酷,因为这可以加快测试速度。

另一方面,如果您需要在组件中使用浏览器 API,则比在浏览器中更难。幸运的是,jsdom has support for canvas。您只需配置它:

jsdom includes support for using the canvas package to extend any <canvas> elements with the canvas API. To make this work, you need to include canvas as a dependency in your project, as a peer of jsdom. If jsdom can find the canvas package, it will use it, but if it's not present, then <canvas> elements will behave like <div>s.

或者,您可以用一些基于浏览器的测试 运行 替换 Jest,例如 Karma. Jest is pretty buggy

我遇到了完全相同的问题。我正在部署到 gitlab ci 到 运行 我的测试,并且由于 npm canvas 需要安装 Cairo,所以使用它不是一个可行的选择。

我真正想做的就是通过 Jest 模拟实现,这样它实际上就不会尝试创建真实的上下文。这是我解决它的方法:

添加到package.json

"jest": {
  "setupFiles": ["./tests/setup.js"],
}

tests/setup.js

import sinon from 'sinon';

const createElement = global.document.createElement;
const FAKECanvasElement = {
  getContext: jest.fn(() => {
    return {
      fillStyle: null,
      fillRect: jest.fn(),
      drawImage: jest.fn(),
      getImageData: jest.fn(),
    };
  }),
};

/**
 * Using Sinon to stub the createElement function call with the original method
 * unless we match the 'canvas' argument.  If that's the case, return the Fake 
 * Canvas object.
 */
sinon.stub(global.document, 'createElement')
  .callsFake(createElement)
  .withArgs('canvas')
  .returns(FAKECanvasElement);

对于我的用例,我做了像这样的简单猴子修补

beforeEach(() => {
    const createElement = document.createElement.bind(document);
    document.createElement = (tagName) => {
        if (tagName === 'canvas') {
            return {
                getContext: () => ({}),
                measureText: () => ({})
            };
        }
        return createElement(tagName);
    };
});

无需安装 canvas-prebuilt 或 sinon。

对于那些寻找使用 create-react-app 的例子的人

安装

yarn add --dev jest-canvas-mock

创建一个新的${rootDir}/src/setupTests.js
import 'jest-canvas-mock';
npm install -D canvas-prebuilt@1

这为 jest.This 提供了对 canvas 的支持,即使有人因 Lottie.js.

而出错时也能正常工作

要测试 canvas 开玩笑的输出,您需要执行以下操作:

确保你至少使用 jsdom 13。你可以通过包含 jest 的 jsom 包来做到这一点,对于 14,它是:

jest-environment-jsdom-fourteen

并配置 jest 以使用此

jest --env=jest-environment-jsdom-fourteen

或在package.json

"jest": {
   ...
   "testEnvironment": "jest-environment-jsdom-fourteen",

包括 canvas npm 包。 (从 2.x 开始,这包括内置版本,因此不推荐使用 canvas-prebuilt)。

我设法从 canvas 开玩笑地使用 react-testing-library 和 jest-image-snapshot 创建了一个图像快照测试。这有点流行,但效果很好。

如果您能够使用 node-canvas(不是 jest-canvas-mock 或类似的)正确设置您的玩笑测试,那么您可以直接调用 toDataURL canvas 元素。


  import {render, waitForElement} from 'react-testing-library'
  import React from 'react'
  import { toMatchImageSnapshot } from 'jest-image-snapshot'

  expect.extend({ toMatchImageSnapshot })

  test('open a canvas', async () => {
    const { getByTestId } = render(
      <YourCanvasContainer />,
    )
    const canvas = await waitForElement(() =>
      getByTestId('your_canvas'),
    )
    const img = canvas.toDataURL()
    const data = img.replace(/^data:image\/\w+;base64,/, '')
    const buf = Buffer.from(data, 'base64')
    expect(buf).toMatchImageSnapshot({
      failureThreshold: 0.001,
      failureThresholdType: 'percent',
    })
  })

我发现我需要使用低阈值而不是直接比较 image/png 数据 URL 因为有两个像素在 travis 上 运行 时随机不同CI

也可以考虑手动升级 jest environment jsdom 到 jest-environment-jsdom-13 或 jest-environment-jsdom-14(本页其他答案建议类似)并参考 https://github.com/jsdom/jsdom/issues/1782

jest-canvas-mock 会很好用。

  1. 安装 npm i --save-dev jest-canvas-mock

  2. 在你的笑话中 jest.config.js 添加 "setupFiles": ["jest-canvas-mock"] 属性。
    (如果您已经拥有 setupFiles 属性,您还可以将 jest-canvas-mock 附加到数组,例如 "setupFiles": ["something-xyz.js", "jest-canvas-mock"])。

全部完成。

如果您使用的是 create-react-app,请使用 npm i --save-dev jest-canvas-mock 安装 jest-canvas-mock 并在测试文件的顶部放置 import 'jest-canvas-mock'

如果安装了 node-canvas 库,Jest / jsdom 可以处理 canvas 个元素。

因此卸载 jest-canvas-mock(如果安装)并安装 canvas:

npm uninstall jest-canvas-mock
npm i --save-dev canvas

在我的例子中,我使用的是 React。

npm uninstall jest-canvas-mock

npm i --save-dev canvas

这两个命令很有用。