Testing P5 with Mocha and Chai

What This Is

This is a walk-through of the test-driven development example on the p5js site. The color changing square above is created by exhaustively stepping through the RGB values for a p5 color. The site documentation doesn't seem to tell you the name of the color array attribute so you can access it, instead it tells you to use setter methods, but the tutorial indicates that the array is stored as an attribute of the color object named levels so we'll be working with that to change the colors.

Setting Up the Tests

Running the Tests

To run the tests I'll use three things:

  • mocha: the framework to define the tests
  • nodemon: a program that watches our code and re-runs mocha if something changes
  • npm: the node-package-manager which will run nodemon to run mocha

npm comes with and works with node so I guess we really need four things.

To run the tests I created a package.json file which points to the folder with the test code in it (or any folder next to the package.json file whose name starts with test).

{
  "scripts": {
    "test": "nodemon --watch . --exec 'mocha ./test* || true'"
  }
}

Weirdly, the mocha Getting Started documentation has an example package.json snippet to run mocha which doesn't put curly braces outside of the "scripts" attribute, but this will cause npm to throw an error saying that it isn't valid JSON. Maybe they figure you'd just know it. Or maybe they expect that you'd install mocha locally so there'd be an existing package.json that you just have to edit. Anyway, to run the tests I go into the folder above the test-folder and run npm like this:

npm test

Writing the Test File

Put It In Strict Mode

Javascript can be pretty lenient about letting what might be considered mistakes pass through without raising an error. I'll put the test-code into Strict Mode, making our mistakes more evident, hopefully, by putting this line at the top of the test.js file:

"use strict";

Import Chai and Sinon

I'm going to use the Behavior-Driven Development (BDD) version of Chai to make our assertions about what we expect the code to be doing.

const expect = require("chai").expect;

I'm also going to stub some methods using sinon.

const sinon = require("sinon");

Note: I originally had the ColorIncreaser class that I'm going to test in the same file as the sketch which caused an error because the sketch refers to a p5 object that gets pulled in from a CDN by an HTML tag. I later decided to move it into its own file so there's no longer a need to mock p5, but if you ever need to do it you can add this line before importing anything from the sketch file:

global.p5 = sinon.stub();

Import the Color Increaser Class

We're going to create a ColorIncreaser class which we need to import into the test code. Our import is going to look a little funky, though, because the sketch is in a folder created for this post.

  • files/posts/bdd-testing-p5/

But the test-code is in a different folder.

  • tests/mocha-tests/test-bdd-testing-p5/

This folder sits next to the files folder. So our test code has to go up two levels and then back down into the files to get the sketch code.

const ColorIncreaser = require('../../../files/posts/bdd-testing-p5/color-increaser');

Note that the path is relative to where the javascript file with the tests is, not where I'm running mocha. Also, although it looks like a file reference, the file is called color-increaser.js, but we're treating it as a module so we don't put the file extension on it.

Also, in order to be able to import the class we need to set it as exportable in the file where the class is defined - but I'll show that after the class is defined.

The Whole Code Module

Since this is about coding test-driven I'm going to show the tests and then the implementation that gets written to pass the test. This makes it kind of disorienting to read so I thought I'd show the entire color-increaser.js file that's being created here exactly as it ends up once it's all done.

const CHANNELS = {
  RED: 0,
  GREEN: 1,
  BLUE: 2,
  ALPHA: 3,
  MAXIMUM: 255,
  MINIMUM: 0
};

class ColorIncreaser {
  constructor(color_increment, color) {
    this.color_increment = color_increment;
    this.color = color;
  }// end constructor

  increase() {
    this.color.levels[CHANNELS.RED] += this.color_increment;

    if (this.color.levels[CHANNELS.RED] > CHANNELS.MAXIMUM) {
      this.color.levels[CHANNELS.RED] = CHANNELS.MINIMUM;
      this.color.levels[CHANNELS.GREEN] += this.color_increment;
    };

    if (this.color.levels[CHANNELS.GREEN] > CHANNELS.MAXIMUM) {
      this.color.levels[CHANNELS.GREEN] = CHANNELS.MINIMUM;
      this.color.levels[CHANNELS.BLUE] += this.color_increment;
    };

    if (this.color.levels[CHANNELS.BLUE] > CHANNELS.MAXIMUM) {
      this.color.levels[CHANNELS.BLUE] = CHANNELS.MINIMUM;
    };
  } // end increase

  next() {
    this.increase();
    return this.color;
  };  //end next
}; // end ColorIncreaser

if (typeof module != "undefined") {
  module.exports = ColorIncreaser;
};

Channel Constants

I have a tendency to flip values around (sometimes it seems like blue should come before green, for example) so I like to create objects to hold values that get used repeatedly. This at the top of the same module as the Color Increaser class.

const CHANNELS = {
  RED: 0,
  GREEN: 1,
  BLUE: 2,
  ALPHA: 3,
  MAXIMUM: 255,
  MINIMUM: 0
};

Testing and Implementing the Color Increaser

Now we'll write the tests and implement the ColorIncreaser class along with it.

Mock Color

We don't want to use any p5 objects in the testing so we'll make a fake color object, implementing its undocumented (as far as I can tell) levels attribute as an Array.

class MockColor {
  constructor(red, green, blue, alpha){
    this.levels = [
      red,
      green,
      blue,
      alpha
      ]
  } // end constructor
 }// end mock_color

Setting Up the Tests

mocha's describe function acts like a header around our tests. Within it I'm setting some constants and also defining a beforeEach function which will create a new ColorIncreaser object before running each test so it won't have any changes we make in the other tests spilling over. Although I could import the CHANNELS object with the constants I thought it'd be a better check to create a new set to double-check what's going on in the code being tested.

describe('Given the ColorIncreaser class definition', function() {
  const [RGB_MIN, RGB_MAX ]= [0, 255];
  const [RED, GREEN, BLUE, ALPHA] = [0, 1, 2, 3];
  const CHANNELS = [RED, GREEN, BLUE, ALPHA];
  const COLOR_INCREMENT = 1;

  let color_increaser;
  let color_mock;

  beforeEach(function() {
    color_mock = new MockColor(RGB_MIN, RGB_MIN,
                               RGB_MIN, RGB_MAX);
    color_increaser = new ColorIncreaser(COLOR_INCREMENT,
                                         color_mock);
  });

Does the ColorIncreaser Exist?

Our first test makes sure that we were able to create the ColorIncreaser object. This is actually a little bit of a fake because the beforeEach will fail if we can't create the object and it won't actually reach this test. I suppose you could still break it by making a function named ColorIncreaser instead of a class so this acts as an extra check.

First we make a section header to make it a little clearer (it doesn't actually affect the tests, just the output after the test is run).

describe("When the ColorIncreaser is constructed",
          function() {

And then the actual test to see if the constructor created an object.

it ("Then it should be an object", function(done) {
  expect(color_increaser).to.be.a('object');
  done();
});

Mocha will run the tests asynchronously (parallel and out of order) so we need to accept the done function and then call it when the test is finished so mocha knows that the test is done.

The ColorIncreaser Class

Now that we have the test I'll define the class and its constructor. It's a pretty basic class, here's a class diagram of its attributes.

Color Increaser class diagram

And here's the class and constructor definition.

class ColorIncreaser {
  constructor(color_increment, color) {
    this.color_increment = color_increment;
    this.color = color;
  }// end constructor

Does it take an increment amount?

This is just a sanity check to make sure that the constructor actually saved the increment amount we passed in.

it("And it should set the color_increment",
   function(done){
     expect(color_increaser.color_increment).to.equal(COLOR_INCREMENT);
     done();
   });

Does it take a color?

This is another check to make sure the constructor saved the color object that got passed in. I couldn't find a same-object checker in chai but the eql is described as a check that the objects are "deeply-equal" which I assume is the same thing.

it("And set the color attribute",
  function(done){
    expect(color_increaser.color).to.be.eql(color_mock);
    done();
  });

The Increaser

We're going to implement a method for the ColorIncreaser that will increment the color-array so let's check that it works. It sort of treats the three color channels (Red, Green, and Blue… we'll pretend Alpha doesn't exist) like a three-digit number using base 255 instead of 10 and with the digits being BGR. So we start with all three channels at 0, then we increment red from 0 to 255 and when it hits 256 we set it back to 0 and set green to 1, then we repeat this over and over until green goes up to 256 at which point we reset both red and green to 0 and set blue to 1 and so on.

I'll add a describe section to the tests to make it clearer where testing this method starts.

describe("And when the increase() method is called", function() {

Does the red channel increase?

Until it increments the red channel past 255 the other channels don't change so we'll first test that only the red channel changes up until that point.

it ("Then it should increment red up until 255",
    function(done){
      for (let count=RGB_MIN; count < RGB_MAX; count +=1) {
        color_increaser.increase();
      }
      let expected = [RGB_MAX, RGB_MIN, RGB_MIN, RGB_MAX]

      CHANNELS.forEach((channel, index) => expect(
        color_increaser.color.levels[channel]).to.equal(expected[index]));

      done();
    });

The Color-Increaser Method

So now I'll add the increase method to the ColorIncreaser class which adds the color_increment to the red-channel when it's called.

increase() {
  this.color.levels[CHANNELS.RED] += this.color_increment;

Does it wrap red back to 255 when it hits 256?

The channels can only go up to 255 so once we hit 256:

  • red wraps back to 0
  • green increments one

The tutorial uses for-loops to actually step through and increment the color until it passes the limit but I'll cheat and just set the red-channel to the limit and then increment it.

These are our expected values before and after the call to increase.

Channel Before Increase After Increase
Red 255 0
Green 0 1
Blue 0 0
Alpha 255 255
it ("But red should wrap-around to 0 when it hits 256 and increment green",
    function(done) {
      color_increaser.color.levels[RED] = RGB_MAX;
      color_increaser.increase();
      let expected = [RGB_MIN, COLOR_INCREMENT,
                      RGB_MIN, RGB_MAX];
      CHANNELS.forEach((channel, index) => expect(
        color_increaser.color.levels[channel]).to.equal(expected[index]));
      done();
    });

The Wrap-Red Conditional

Within the increase method we add this little chunk after incrementing the red channel.

if (this.color.levels[CHANNELS.RED] > CHANNELS.MAXIMUM) {
  this.color.levels[CHANNELS.RED] = CHANNELS.MINIMUM;
  this.color.levels[CHANNELS.GREEN] += this.color_increment;
};

Does Green Wrap Too?

As with red, the green channel can only go up to 255 so once we increase green to 256:

  • wrap green back to 0
  • increment blue

Also, since we only increment green if red hits 256 it wraps back to 0 too. Once again, I'm forgoing looping and incrementing and instead just setting the red and green channels to their limits and then calling increase.

These are our expected values before and after the call to increase.

Channel Before Increase After Increase
Red 255 0
Green 255 0
Blue 0 1
Alpha 255 255
it("And it should wrap-around green and increase blue if green exceeds 255",
   function(done) {
     color_increaser.color.levels[RED] = RGB_MAX;
     color_increaser.color.levels[GREEN] = RGB_MAX;

     color_increaser.increase();

     let expected = [RGB_MIN, RGB_MIN,
                     COLOR_INCREMENT, RGB_MAX];

      CHANNELS.forEach((channel, index) => expect(
        color_increaser.color.levels[channel]).to.equal(
          expected[index]));
    done();
   });

The Wrap-Green Conditional

Now within the increase method we add this little chunk after incrementing the red channel to check the green channel.

if (this.color.levels[CHANNELS.GREEN] > CHANNELS.MAXIMUM) {
  this.color.levels[CHANNELS.GREEN] = CHANNELS.MINIMUM;
  this.color.levels[CHANNELS.BLUE] += this.color_increment;
};

Does Blue Wrap Too?

As with red and green, the blue channel can only go up to 255 so once we increase blue to 256:

  • wrap all three channels back to 0

These are our expected values before and after the call to increase.

Channel Before Increase After Increase
Red 255 0
Green 255 0
Blue 255 0
Alpha 255 255
it("And it should wrap-around all colors when blue exceeds 255",
   function(done) {
     CHANNELS.forEach(
       channel => color_increaser.color.levels[channel] = RGB_MAX);

     color_increaser.increase();

     let expected = [RGB_MIN, RGB_MIN, RGB_MIN,
                     RGB_MAX];

      CHANNELS.forEach((channel, index) => expect(
        color_increaser.color.levels[channel]).to.equal(
          expected[index]));

    done();
   });

The Wrap-Blue Conditional

I originally just used a modulus to keep blue within range, but since we're allowing the user to change the increment this might not always go back to zero so here's the conditional that resets the blue channel if it exceeds the limit.

if (this.color.levels[CHANNELS.BLUE] > CHANNELS.MAXIMUM) {
  this.color.levels[CHANNELS.BLUE] = CHANNELS.MINIMUM;
};

And there we have our exhaustive channel incrementer.

The Next Method

I'm going to implement a next method to simplify things a little. It'll call increase and the return the color object. Just because.

describe("And when the next() method is called", function() {

Does the next method increase the color?

To check that next calls the increase method I'll monkey-patch it with a sinon spy and check that when we call next the increase spy gets called.

it("Then it should increase the color",
  function(done) {
    let spy = sinon.spy(color_increaser, "increase");
    color_increaser.next();
    expect(spy.calledOnce).to.be.true;
    spy.restore();
    done();
  });

The Next Method

Now I'll implement the next method and have it call the increase method.

next() {
  this.increase();

Does next return the color?

Instead of requiring the user to make two calls I'll have the next method return the color object directly.

it ("And return the color",
    function(done) {
      let actual = color_increaser.next();
      expect(color_increaser.color).to.be.eql(actual);
      done();
    });

Return the color

Now in our ColorIncreaser.next method we return the color.

return this.color;

Export the Color Increaser

The implementation so far is good enough to get the code working within the p5 sketch, which would normally be the ultimate goal. But in order for the test code to be able to import the ColorIncreaser (using the require function) we need to add a line in the file with the ColorIncreaser definition to export it.

The code given in the tutorial just has the export line, but this will raise an error outside of node.js - so it works for testing but causes a ReferenceError in the browser (when using a python-based server anyway) so to prevent that from happening I added a check that will do the export only if the module is defined, which is an indication that this is being used in node (or I messed up and defined it somewhere else).

if (typeof module != "undefined") {
  module.exports = ColorIncreaser;
};

Running The tests

Now, let's see what it looks like when we run the tests.

mocha ../tests/test*


Given the ColorIncreaser class definition
  When the ColorIncreaser is constructed
    ✔ Then it should be an object
    ✔ And it should set the color_increment
    ✔ And set the color attribute
  And when the increase() method is called
    ✔ Then it should increment red up until 255
    ✔ But red should wrap-around to 0 when it hits 256 and increment green
    ✔ And it should wrap-around green and increase blue if green exceeds 255
    ✔ And it should wrap-around all colors when blue exceeds 255
  And when the next() method is called
    ✔ Then it should increase the color
    ✔ And return the color


9 passing (11ms)

The output in the terminal has some color added to the text so it looks prettier, but otherwise that's the output of running our tests.

The Sketch

Now that we have the ColorIncreaser we can add it to a sketch to change the colors of the square that we're drawing.

The Sketch Function

As I noted before, I'm using the instance-mode syntax for the sketch so this is the function that holds the setup and draw functions.

function flashing_square(p5js) {
  const SIZE = 500;

  let color_increaser;

The setup Function

The setup creates the canvas and color increaser instance.

p5js.setup = function() {
  p5js.createCanvas(SIZE, SIZE);
  color_increaser = new ColorIncreaser(
    1,
    p5js.color(
      CHANNELS.MINIMUM,
      CHANNELS.MINIMUM,
      CHANNELS.MINIMUM,
      CHANNELS.MAXIMUM));
  p5js.background(color_increaser.color);
};

The draw Function

The draw sets the background color and increases its value once per frame.

p5js.draw = function() {
  p5js.background(color_increaser.next());
};

The End

So, there you go. The sketch wasn't so exciting, but the main value in going through the exercise, I think, was being able to get the javascript testing infrastructure working and getting back into using chai and sinon and the crazy world of module imports in javascript.

Sources