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

时间:2015-10-21 20:57:26

标签: reactjs html5-canvas jsdom jestjs

在我的Jest单元测试中,我正在渲染一个ColorPicker的组件。 ColorPicker组件创建画布对象和2d上下文但返回'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"

我遇到了麻烦,弄清楚为什么我无法获得二维背景。我的测试配置可能有问题吗?

"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
}

9 个答案:

答案 0 :(得分:12)

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

另一方面,如果您的组件中需要浏览器API,那么在浏览器中就更难了。幸运的是,jsdom has support for canvas。你只需要配置它:

  

jsdom支持使用canvas包来使用canvas API扩展任何<canvas>元素。为了使这项工作,您需要将canvas作为依赖项包含在项目中,作为jsdom的同行。如果jsdom可以找到canvas包,它将使用它,但如果它不存在,那么<canvas>元素将表现得像<div> s。

或者,您可以使用Karma之类的基于浏览器的测试运行器替换Jest。无论如何,Jest是pretty buggy

答案 1 :(得分:5)

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

安装

yarn add --dev jest-canvas-mock

使用

创建一个新的${rootDir}/src/setupTests.js
import 'jest-canvas-mock';

答案 2 :(得分:3)

我有完全相同的问题。我部署到gitlab ci来运行我的测试,因为npm canvas需要安装Cairo,使用它不是一个可行的选择。

我真正想做的就是通过Jest模拟实现,以便它实际上并没有尝试创建真实的上下文。以下是我如何解决它:

已添加到package.json

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

测试/ 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);

答案 3 :(得分:3)

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

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

无需安装canvas-prebuilt或sinon。

答案 4 :(得分:1)

要开玩笑地测试画布输出,您需要执行以下操作:

确保您至少使用jsdom13。您可以通过加入jsom包来进行开玩笑,其中14个是:

jest-environment-jsdom-fourteen

并配置玩笑以使用

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

或在package.json

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

包括canvas npm软件包。 (从2.x版本开始,此版本包含构建版本,因此不建议使用canvas预构建版本)。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:0)

npm install -D canvas-prebuilt@1

这为jest提供了对canvas的支持。即使由于Lottie.js而出错,这也可以使用。

答案 8 :(得分:0)

我设法通过用react-testing-library和jest-image-snapshot在画布上创建了一个图像快照测试。有点流行,但效果很好。

如果您能够使用节点画布(不是jest-canvas-mock或类似的东西)正确设置笑话测试,则可以从字面上调用canvas元素上的toDataURL。


  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环境jsdom手动升级到jest-environment-jsdom-thirteen或jest-environment-jsdom-fourteen(此页面上的其他答案类似),并参考https://github.com/jsdom/jsdom/issues/1782