Unit Testing an HTML5 Canvas with Canteen
03/2015 by D. NutterWhat is Canteen?
The Canteen home page describes the project as follows:
[...Canteen...] records all of the drawing instructions, such as method calls and property changes, by creating a wrapper around the HTML5 Canvas context object that records all of these instructions and then proxies them to the native HTML5 Canvas context for rendering.
I use Canteen to monitor the HTML5 canvas interface to build unit tests. Canteen keeps track of all the drawing commands sent to the canvas and you can export the commands as an array of the canvas instruction stack, JSON, or md5sum. This yields a repeatable canvas structure that you can compare to for unit testing. Canteen offers a simpler way to test HTML5 canvas without having to do image diffs, image recognition, or something more complex with image or SVG exports of the canvas.
Why would I want to unit test the canvas object?
When unit testing the canvas object, Canteen offers a way to repeatedly verify that the drawing on the canvas executes the same canvas instructions as when you developed the canvas codebase. I develop what I want to draw on the canvas and use Karma and Jasmine to run tests against it.
Installing Jasmine, PhantomJS, AngularJS, and Canteen
For development I use a Windows 7 x64 machine. Here are some specific notes on using node.js with Windows 7. If you are running Windows 7 then install the Windows SDKs, if you are running some other OS then ignore the instructions for Windows 7.
First, install Node.js for your platform.
If you are running Windows 7 open a Node.js command prompt, and execute either
call "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\bin\\Setenv.cmd" /Release /x86
for x86 Windows 7 or\
call "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\bin\\Setenv.cmd" /Release /x64
for x64 Windows 7.
Then install AngularJS and Angular Mocks:
npm install angular angular-mocks
Then install PhantomJS and Jasmine
npm install phantomjs jasmine
Then install Karma Runner and the associated Karma Plugins for Jasmine and PhantomJS
npm install karma karma-jasmine karma-phantomjs-launcher --save-dev
Also, under Windows 7, ignore any warnings when installing.
Clone the Canteen github repository (you must have git installed if you don’t already)
git clone https://github.com/platfora/Canteen.git
Karma Configuration File
// Karma configuration
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: 'D:/projects/',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
// referenced libraries
'node_modules/angular/angular.min.js',
'node_modules/angular-mocks/angular-mocks.js',
'Canteen/build/canteen.js',
// application files
'canvas.module.js',
// unit tests
'test.canvas.module.js'
],
// list of files to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
});
};
AngularJS Code To Test
The following AngularJS service draws a circle on a canvas.
angular.module("lec.canvas", [])
.factory('lec.canvas.service.draw', function() {
// the HTML5 drawing context and items to set defaults for
var _canvas, _context;
return {
// initializes parameters for the segmented drawing service
initialize: function(canvas) {
_canvas = canvas;
_context = _canvas.getContext('2d');
// clears the canvas, and resets vars
this.clear();
},
context: function() {
return _context;
},
// clear the canvas of any previous drawings
clear: function() {
_context.clearRect(0, 0, _canvas.width, _canvas.height);
},
// draws a circle on the canvas
circle: function(center, radius, color) {
_context.beginPath();
_context.arc(center.x, center.y, radius, 0, 2*Math.PI, false);
_context.lineWidth = 2;
_context.strokeStyle = color;
_context.stroke();
},
};
});
Jasmine Tests
The following is the jasmine test developed to test the service that draws the circle.
describe('lec.canvas', function() {
// test the segmented ring drawing functionality
describe('lec.canvas.service.draw', function() {
var draw;
beforeEach(function() {
// load the module.
module('lec.canvas');
inject(function($injector) {
draw = $injector.get('lec.canvas.service.draw');
var canvas = document.createElement('canvas');
canvas.id = "testCanvas";
canvas.width = 500;
canvas.height = 500;
// initialize the canvas for drawing
draw.initialize(canvas);
});
});
describe('clear', function() {
it('erases the canvas', function() {
draw.clear();
var expected = 'c62e5ecf5bead7be72f252325e55bc02';
var hash = draw.context().hash();
//console.log('json: ' + json);
//console.log('hash: ' + hash);
// example unit test assertion
expect(hash).toBe(expected);
// clear the stack
draw.context().clean();
});
});
describe('circle', function() {
it('creates a circle', function() {
var expected = '';
draw.circle({x: 250, y: 250}, 100, 'red');
json = draw.context().json();
hash = draw.context().hash();
console.log('json: ' + json);
console.log('hash: ' + hash);
// example unit test assertion
expect(hash).toBe(expected);
// clear the stack
draw.context().clean();
});
});
});
});
Test Runs
Execute the unit test with:
karma start canvas.conf.js
The first test run is an unsuccessful test, and the code will output the JSON and md5sum representing the canvas generated.
[32mINFO [karma]: [39mKarma v0.12.31 server started at http://localhost:9876/
[32mINFO [launcher]: [39mStarting browser PhantomJS
[32mINFO [PhantomJS 1.9.8 (Windows 7)]: [39mConnected on socket nCQUhrfMg-pT57ifPpfN with id 9109488
PhantomJS 1.9.8 (Windows 7): Executed 0 of 2[32m SUCCESS[39m (0 secs / 0 secs)
[1A[2KPhantomJS 1.9.8 (Windows 7): Executed 1 of 2[32m SUCCESS[39m (0 secs / 0.024 secs)
[1A[2KLOG: [36m'json: [{"method":"clearRect","arguments":[0,0,500,500]},{"method":"beginPath","arguments":[]},{"method":"arc","arguments":[250,250,100,0,6.283185307179586,false]},{"attr":"lineWidth","val":2},{"attr":"strokeStyle","val":"red"},{"method":"stroke","arguments":[]}]'[39m
PhantomJS 1.9.8 (Windows 7): Executed 1 of 2[32m SUCCESS[39m (0 secs / 0.024 secs)
[1A[2KLOG: [36m'hash: 00f624e6dff2bf91662a1f80f6981ad9'[39m
PhantomJS 1.9.8 (Windows 7): Executed 1 of 2[32m SUCCESS[39m (0 secs / 0.024 secs)
[1A[2K[31mPhantomJS 1.9.8 (Windows 7) lec.canvas lec.canvas.service.draw circle creates a circle FAILED[39m
Expected '00f624e6dff2bf91662a1f80f6981ad9' to be ''.
at D:/projects/Websites/content/entropteria/articles/canteen/codebase/test.canvas.module.js:50
PhantomJS 1.9.8 (Windows 7): Executed 2 of 2[31m (1 FAILED)[39m (0 secs / 0.033 secs)
[1A[2KPhantomJS 1.9.8 (Windows 7): Executed 2 of 2[31m (1 FAILED)[39m (0 secs / 0.033 secs)
The json canvas command result and the hash of the commands were printed out with “console.log”. You can now base a test off of either the JSON or md5sum results. I chose the md5sum, and assigned the md5sum ‘00f624e6dff2bf91662a1f80f6981ad9’ in the ‘expected’ string, commented out the console.log() commands, and the result is below.
[32mINFO [karma]: [39mKarma v0.12.31 server started at http://localhost:9876/
[32mINFO [launcher]: [39mStarting browser PhantomJS
[32mINFO [PhantomJS 1.9.8 (Windows 7)]: [39mConnected on socket R1DzjSZ7qVhKA0l7RGp8 with id 11436839
PhantomJS 1.9.8 (Windows 7): Executed 0 of 2[32m SUCCESS[39m (0 secs / 0 secs)
[1A[2KPhantomJS 1.9.8 (Windows 7): Executed 1 of 2[32m SUCCESS[39m (0 secs / 0.024 secs)
[1A[2KPhantomJS 1.9.8 (Windows 7): Executed 2 of 2[32m SUCCESS[39m (0 secs / 0.03 secs)
[1A[2KPhantomJS 1.9.8 (Windows 7): Executed 2 of 2[32m SUCCESS[39m (0 secs / 0.03 secs)
Conclusion
I really like Canteen as it offers a way to test HTML5 canvas in a simpler way. To view an example of a more complex web application I developed using Canteen, see this.