How and why we test JavaScript – Jasmine and Teaspoon

(Everyday Code – instead of keeping our knowledge in a README.md let’s share it with the internet)

We are serious about specs. A good suite of specs has helped us many times especially when it comes to regressions. For the JavaScript part of our stack many of our specs and using Jasmine and ran with Teaspoon in the Browser environment. This article is about how we use Teaspoon and Jasmine to run our specs for pure JavaScript frameworks like Instructions Steps, and BABYLON.js. Both BuildIn3D and FLLCasts are powered by this specs, but we use them mostly for the Instructions Steps Framework.

Why JavaScript specs?

In the Rails community specs are important. In the JavaScript community – well, they are not that well respected. We can debate the reasons, but this is our observation.

When we implement specs for the platforms we like to use Rails system specs. They give us exactly what we need. Let’s take a real life example.

Filtering by category

We visit the page at BuildIn3D for all the 3D models & building instructions. We filter them by brand. We click on a brand and see only the 3D models & instructions for this specific brand.

Here is the spec. Short and sweet.

scenario "/instructions shows the instructions and can be filted by category", js: true do
  # go to the third page as the materials are surely not there
  visit "/instructions?page=3" 
  expect(page).not_to have_link material1.title

  click_category category1

  # We make sure the url is correct
  expect(page).to have_current_path(/\/instructions\?in_categories%5B%5D=#{category1.id}$/, url: true)

  # We make sure that only material1 is shown and material2 is not shown
  # The materials are filtered by category
  expect(page).to have_link material1.title
  expect(page).not_to have_link material2.title
endT

The spec is a Rails system spec. Other articles enter into more details about them. The point is:

With a Rails system spec we don’t concern ourselves with JavaScript.

We visit the page, we click, we see that something has changed on the page – like new materials were shown and others were hidden.

What’s the use case for JavaScript specs then?

Take a look at the following embedded BuildIn3D instruction. It is coming live and it has a next button. Click on the next button.

TRS the Turning Radio Satellite construction from GeoSmart and in 3D

Here is the actual spec in JavaScript with Jasmine and ran everyday with Teaspoon.

it("right arrow button triggers TriedMoveIterator with 1", function(done) {
    // This is a JavaScript spec that is inside the browser. It has access to all the APIs 
    // of the browser. 
    const eventRight = new KeyboardEvent("keydown", { key: "ArrowRight" });
    document.onkeydown(eventRight);
    
    // Wait for a specific event to occur in the Instructions Steps (IS) framework
    // We are inside the browser here. There is no communication with the server

    IS.EventsUtil.WaitFor(() => this.iteratorListener.event).then(() => {
      expect(this.iteratorListener.event.getSteps()).toEqual(1);
      done();
    });
  });

This the how we use JavaScript and this is why we need them:

We need JavaScript specs that are run inside the browser to communicate with other JavaScript objects and the browser APIs for JavaScript apps.

In this specific case there is this “iteratorListener” that monitors how the user follows the instructions. We need access to it. Otherwise it gets quite difficult to test. We can not write a spec to test what are the pixels on the screen that are drawn after clicking next. This will be … difficult to put it mildly. We don’t have to do it. We need to know that clicking the next button has triggered the proper action which will then draw the actual Geometry and Colors on the screen.

How we use Jasmine and Teaspoon to run the JavaScript specs

Years ago I found a tool called Teaspoon. Looking back this has been one of the most useful tools we’ve used in our stack. It allows us to have a Rails project (and we have a lot of those) and to run JavaScript specs in this Rails project. The best thing – it just works (the Rails way).

# add teaspoon dependency
$ cd test/dummy/
$ rails generate teaspoon:install
# write a few specs
$ rails s -p 8889

You start a server, visit the url in the browser and the specs are executed

Tests are pure JavaScript and we use Jasmine.

That’s it. Not much to add. It’s simple The specs are a simple JS file located in “spec/javascripts/” folder and here is an example for one of them

describe("IS.EventDef", function() {
  describe("constructing throws error if", function() {
    it("event name is null", function() {
      expect(() => new IS.EventDef(null, {})).toThrow(new Error("Null argument passed."));
    });

    it("event conf is null", function() {
      expect(() => new IS.EventDef("smoe", null)).toThrow(new Error("Null argument passed."));
    });

    it("declaring an event, but the class attribute value is null", function() {
      expect(() => {
        const ext = IS.ExtensionDef.CreateByExtensionConf({
          extension: new IS.Extension(),
          events: {
            declaredEvent: {
              class: null
            }
          }
        });
      }).toThrow("Declared event 'declaredEvent' class attribute is null!");
    });
  });
});