Canvas Testing with Angular

Testing canvas elements with jest and snapshots

Posted by Gregor Tätzner


Just a small writeup for my fellows working with HTML Canvas elements and trying to write some meaningful tests for your angular application.

paintings-316440 1280

Some time ago I started working on a legacy application (means no tests) and tried to increase the test coverage. Everything was working nice and progressing slowly, since the previous developer decided to use a redux architecture, so most of the components were quite testable. The app renders a background image on which you can draw all kind of figures. Sooner or later I stumbled across this component, which was using a plain html canvas. So what to do…

Jest to the rescue

Maybe you know Facebook with their testing framework jest, very famous in the React world. As I was skipping over the documentation I found this really interesting feature called Snapshot Testing. And I asked myself: Does it work with canvas elements too? Yes it does! You just need to add an extra plugin, the snapshot serializer. Sounds great and all, I can draw something on the canvas, run the test (which autogenerates a snapshot/image of the canvas) and in future the test fails if the drawing somehow regresses.

Luckily you can also use jest with Angular. Since karma and jasmine sucks this is probably a smart move anyway. Jsdom gets you more isolation and concurrent tests for free! To setup jest you can follow one of many guides, I think the article from Johan Axelsson is nice

Sample test

Of course you want to see some dirty code, so lets have a look at this sample:

// don't forget to register the new serializer at the top of your test file
import * as canvasSerializer from 'jest-canvas-snapshot-serializer'
expect.addSnapshotSerializer(canvasSerializer)

it('should colorize segment image', done => {
  component.draw.segmentImageDataUrlChange.subscribe(() => {
    expect(component.CanvasElement.nativeElement).toMatchSnapshot()
    done()
  })

  component.segmentImage = testImage
  component.size = { width: 100, height: 100 }
  component.recolorize = true
  component.canvasColor = '#aa0000'
})

So this looks like an ordinary testcase. There is some async action going on but in the end only one line matters: expect(component.CanvasElement.nativeElement).toMatchSnapshot()

Here the snapshot serializer reads the canvas and compares it against a stored snapshot. They are right next to the spec file, you can find a *.snap file in the __snapshots__ folder. It includes the stored snapshot in text form:

exports[`SegmentEditorPaneComponent should colorize segment image 1`] = `
<canvas
  class="canvas"
  data-snapshot-image="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAABAklEQVR4nO3RQREAIRDAsOWMoweXvE8BfSQKOtN1ZvaQ8b0O4M+QGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiLvXdAfCvy8aXAAAAAElFTkSuQmCC"
  height="100"
  width="100"
/>
`;

And for our human friends also as a nice picture:

1*KSKbg7Iu-Xb0749R2l1WCg

Granted, this doesn’t seem too fancy. But you get the basic picture. No more excuses not to test your code! If you have any questions or other ideas drop in a comment! Happy testing :)