Unit test on HTML5 canvas

Unit test on HTML5 canvas
The HTML5 canvas allows you to do a lot of things: display and manipulate images, draw shapes, etc. It is mainly used today to make beautiful animations on websites or to create video games.
What about unit tests?
 

Introduction to canvas

The HTML5 canvas element allows you to create 2D or 3D graphic elements. Advantages over graphics rendering software Adobe Flash are that the canvas does not require plugin installation and that the development language is the JavaScript. However, you must have a fairly recent browser (it can be used from IE 9) to be able to use it correctly.
The possibilities offered by the canvas are diverse and varied, here are some examples:

I'm sure that after seeing all this, you want to use or continue to use canvases to make nice animations or games. But, what about unit tests and above all how can we ensure that our tests allow us to certify that the rendering meets our expectations? That's what we're going to find out right now.

Practice: tic-tac-toe

Do you know tic-tac-toe (“Morpion” in French)? This is a simple game where we have to line up 3 crosses or 3 circles to win. This very simple mini-game will allow us to discover how to do unit tests and above all to ensure that the rendering meets our expectations. Here are the things we need to start testing:

Rest assured, it is not necessary to look and know in detail the functions created. Nevertheless, be aware that when we develop on canvas, we quickly distinguish that our code can be separated into two very distinct parts: the logical and made. We will see how to test these two parts.

Installation of the test environment

Jest: the do-it-all testing tool

It's time to get hands on and set up our test environment. To do this, we will use Jest. This library developed by Facebook is an “Out of the box” library, which means that we don't need to install other libraries for it to work and it requires very little configuration. Jest is very complete because it encapsulates robust and useful modules such as:

  • Karma for running tests
  • JSDom to “mock” your DOM
  • Istanbul to know the coverage of your tests

To install Jest, just run this command on your project:

yarn add --dev jest

Here you are, you can now start writing your first test:

// app.test.js
const App = require("./../App");
describe("App", () => {
  it("should be initialized", () => {
    const canvas = document.createElement("canvas");
    canvas.id = "canvas";
    canvas.width = 200;
    canvas.height = 200;
    const app = new App();
    expect(app.canvas).not.toBeNull();
  });
});

This first small test is just to make sure that our application is built well and that it recovers the canvas that we have created. But, if you ever run this test, you will quickly end up with this error: “Cannot read property 'getContext' of null”.
However, the canvas element was successfully created and “app.canvas” is not null ...
But rest assured, this problem is normal because JSDom only mocks the creation of an element but does not allow to emulate the functionality of the canvas. This is why the function “getContext” does not exist. It is therefore, at first sight, impossible to test interactions with canvases. Fortunately, a bookstore is there to solve this problem...

Node-canvas to the rescue

Automattic has created an excellent library to overcome this problem: node-canvas. This is a port of the library Cairo Graphics.
This one being developed in C, you will however need to do a few more commands for it to work.
For example, if you're on a mac, you'll need to run these two commands:

$ yarn add canvas
$ brew install pkg-config cairo pango libpng jpeg giflib

If you are on another OS, I invite you to consult the Github page of node-canvas in order to know the commands necessary to install this library.
One of the advantages of using this library is that JSDom takes this library into account. So you don't have need to do nothing else to integrate node-canvas à JSDom.
If you run the test again, it should no longer have an error.

Unit tests

logic tests

This part of unit testing is the simplest because it's nothing more or less than classic tests.
In this part, you will have to make sure that the main features of the noughts and crosses are running correctly: check that your data table is updated, that the points are counted correctly, etc.
Here is an example of a unit test:

it("should return the cell info 0", () => {
   const cell = app.getCellInfo(0, 0);
   expect(cell).toBe(0);
});

In this example, we make sure that the functions that retrieve data from the data table are functional.
Once you have tested all the possible cases of your logic functions, all that remains is to make sure that the rendering corresponds to your expectations...

Rendering tests

Here is the part that interests us the most. Here, the goal will be to ensure that the rendering meets our expectations in the various cases that may occur. At first, you will have to ask yourself and think about all the possible cases:

  • Displaying a cross in the right place
  • The display of a circle in the right place
  • The display of a line that will cross all the pawns that won the game.
  • The removal of the display of pawns when starting another game.

Facebook has developed a magic tool that allows us to validate the various points mentioned above: Snapshots.
Snapshots are JavaScript files that will capture the information we give it and will compare it between the new captures that will be made at each test:

  • When we run the test for the first time, the captured data will be stored in a JavaScript file provided for this purpose.
  • When we run a second test, the newly captured data will be tested with the stored data to be sure that the two pieces of information are identical.
  • If ever the information is not identical, the tests fail. The new result obtained may be the expected result. For this, you need to update your Snapshots data file by entering the following command:
    jest -u

Now let's see a practical case:

it("should render the board", (done) => {
  app.start();
  app.canvas.toDataURL(type, (err, base64) => {
    expect(base64).toMatchSnapshot();
    done();
  });
});

There you go ! We transform the content of the canvas to base64 in order to compare it to the Snapshots. That's it !

To learn more

Code coverage

canvas-unit-testing_coverage
One of the advantages of Jest is its implementation of Istanbul, as mentioned above. This allows you to know the coverage of your code. Here, no need for configuration or library installations of any kind, you just need to add this in the configuration of Jest package.json :

"jest": {
    "collectCoverage": true,
    "coverageReporters": [
      "json",
      "html"
    ]
}

There you go !
Now when you launch a test, a “coverage” folder will appear at the root of your project and you only have to double-click on the “index.html” file to view the coverage of your code. In order to obtain more information, I invite you to consult the Istanbul doc here.

Continuous integration with Travis CI

Travis C.I. is a tool forContinuous integration. Its purpose is to build your project and run unit tests to ensure there is no regression in your code. To use it, all you have to do is connect to the site using your Github account, then you only have to choose the projects that will use this tool for you to be done. Now all you have to do is configure the process using the file “.travis.yml” that you will need to create at the root of your project. Here is the content of the file in our project:

language: node_js
sudo: required
os:
  - osx
before_install: brew install cairo pango libpng jpeg && brew upgrade pkg-config giflib
node_js:
  - "7.0.0"
  • language: allows you to define the language used in your project (here, node_js)
  • sudo: allows you to activate sudo mode (necessary if you need to install packages, which is our case here)
  • os: allows to choose the execution os (you have the choice between “osx” and “linux”)
  • before_install: allows you to execute shell commands before installing your project on the virtual machine
  • node_js: allows you to specify the version of Node JS that will be used for your tests

NOTE: I voluntarily chose the “osx” language and not “linux” because otherwise the tests will not work. Indeed, the algorithm used to generate the canvas differs between the OSX and Linux version, which means that the Snapshots will probably be different (of the order of a few pixels difference, invisible to the naked eye).

Conclusion

It is important not to neglect unit testing even if it is only used to test the visual rendering.
The Snapshot of Jest is a very powerful tool that is not limited to testing on canvas. It also allows you to test, for example, the visual rendering of your React components. If you want to know more, I invite you to consult this page dedicated to Using Snapshots in React.
Good development to all 🙂