'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.

  1. Install with npm i --save-dev jest-canvas-mock

  2. In 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

Solution Source
Solution 1 Siva
Solution 2 sstauross
Solution 3 Andzej Maciusovic
Solution 4 Donald White
Solution 5 agilgur5
Solution 6 Tom
Solution 7 Hayden Futch
Solution 8 Serenity
Solution 9 Colin D
Solution 10 ouflak