'How to add <canvas> support to my tests in Jest?
In my Jest unit test I am rendering a component with a ColorPicker. The ColorPicker component creates a canvas object and 2d context but returns 'undefined' which throws an error "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"
I'm having troubles figuring out why I can't get a 2d context. Maybe there an issue with my test config?
"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
}
Solution 1:[1]
For those looking for examples using create-react-app
Install
yarn add --dev jest-canvas-mock
Create a new ${rootDir}/src/setupTests.js with
import 'jest-canvas-mock';
Solution 2:[2]
Jest / jsdom can handle canvas elements if the library node-canvas is installed.
Thus uninstall jest-canvas-mock (if installed) and install canvas:
npm uninstall jest-canvas-mock
npm i --save-dev canvas
Solution 3:[3]
For my use case I did simple monkey patching like this
beforeEach(() => {
const createElement = document.createElement.bind(document);
document.createElement = (tagName) => {
if (tagName === 'canvas') {
return {
getContext: () => ({}),
measureText: () => ({})
};
}
return createElement(tagName);
};
});
No need to install canvas-prebuilt or sinon.
Solution 4:[4]
I had the exact same issue. I'm deploying to gitlab ci to run my tests, and since npm canvas requires an installation of Cairo, using that wasn't a viable option.
All I really wanted to do was mock the implementation through Jest so that it doesn't actually try to create a real context. Here's how I solved it:
added to 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);
Solution 5:[5]
jest-canvas-mock will work nice.
Install with
npm i --save-dev jest-canvas-mockIn your jest jest.config.js add
"setupFiles": ["jest-canvas-mock"]attribute.
(If you already have a setupFiles attribute you can also append jest-canvas-mock to the array eg"setupFiles": ["something-xyz.js", "jest-canvas-mock"]).
All Done.
Solution 6:[6]
To test canvas output in jest you need to do the following:
Make sure you are using at least jsdom 13. You can do this by including the jsom package for jest, for 14 it is:
jest-environment-jsdom-fourteen
And configuring jest to use this
jest --env=jest-environment-jsdom-fourteen
or in the package.json
"jest": {
...
"testEnvironment": "jest-environment-jsdom-fourteen",
Include the canvas npm package. (as of 2.x this includes built version so canvas-prebuilt is deprecated).
Solution 7:[7]
If you are using create-react-app, install jest-canvas-mock using npm i --save-dev jest-canvas-mock and at the top of your test files put import 'jest-canvas-mock'
Solution 8:[8]
npm install -D canvas-prebuilt@1
This provides support for canvas for jest.This even works if someone is getting error due to Lottie.js.
Solution 9:[9]
I managed to create an image snapshot test from a canvas in jest with react-testing-library and jest-image-snapshot. That's sort of buzzwordy but it worked nicely.
If you are able to properly setup your jest testing with node-canvas (not jest-canvas-mock or similar) then you can literally call toDataURL on the canvas element.
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',
})
})
I found that I needed to use a low threshold rather than directly comparing the image/png data URL because there were two pixels that were just randomly different when running on travis-CI
Also consider manually upgrading jest environment jsdom to jest-environment-jsdom-thirteen or jest-environment-jsdom-fourteen (other answers on this page suggest similar) and refer to https://github.com/jsdom/jsdom/issues/1782
Solution 10:[10]
I am using react, in my case.
npm uninstall jest-canvas-mock
npm i --save-dev canvas
These two commands are helpful.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
