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:
- Nebulon (by PIXI)
- AutoDraw (by Google Creative Lab)
- Open Lara, a fan-made remake of the first Tomb Raider
- HexGL, a 3D game developed by BKcore
- Z-Type, a game developed by Phoboslab, the creator ofImpactJS
- And many more experiences with Google Chrome Experiments
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:
- The game you can discover in by clicking here
- Source file available here
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:
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
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 🙂