Welcome!

Eclipse Authors: Pat Romanski, Elizabeth White, Liz McMillan, David H Deans, JP Morgenthal

Blog Feed Post

Testing your Frontend JavaScript code using Mocha, Chai and Sinon

Testing your frontend JavaScript code using mocha, chai, and sinon by Nicolas Perriault

This is a republished blog post by Nicolas Perriault. When I first read it I immediately thought it would fit perfectly on our Codeship blog. You may know Nicolas for his awesome CasperJS (an open source navigation scripting & testing utility written in Javascript for PhantomJS) or from various of his talks. We highly recommend following Nicolas on twitter and checking out his website.


As rich Web application complexity grows, if you want to keep your sanity, you need to unit test your frontend JavaScript code.

For the 4 past months, I’ve been working for Mozilla on some big project where such testing strategy was involved. While I wish we could use CasperJS in this perspective, Firefox wasn’t supported at the time and we needed to ensure proper compatibility with its JavaScript engine. So we went with using Mocha, Chai and Sinon and they have proven to be a great workflow for us so far.

The mocha testing framework and the chai expectation library

Mocha is a test framework while Chai is an expectation one. Let’s say Mocha sets up and describes test suites and Chai provides convenient helpers to perform all kinds of assertions against your JavaScript code.

So let’s say we have a Cow object we want to unit test:

// cow.js
(function(exports) {
  "use strict";

  function Cow(name) {
    this.name = name || "Anon cow";
  }
  exports.Cow = Cow;

  Cow.prototype = {
    greets: function(target) {
      if (!target)
        throw new Error("missing target");
      return this.name + " greets " + target;
    }
  };
})(this);

Nothing fancy, but we want to unit test this one.

Both Mocha and Chai can be used in a Node environment as well as within the browser; in the latter case, you’ll have to setup a test HTML page and use special builds of those libraries:

My advice is to store these files in a vendor subfolder. Let’s create a HTML file to test our lib:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Cow tests</title>
  <link rel="stylesheet" media="all" href="vendor/mocha.css">
</head>
<body>
  <div id="mocha"><p><a href=".">Index</a></p></div>
  <div id="messages"></div>
  <div id="fixtures"></div>
  <script src="vendor/mocha.js"></script>
  <script src="vendor/chai.js"></script>
  <script src="cow.js"></script>
  <script>mocha.setup('bdd')</script>
  <script src="cow_test.js"></script>
  <script>mocha.run();</script>
</body>
</html>

Please note we’ll be using Chai’s BDD Expect API, hence the mocha.setup('bdd') call here.

Now let’s write a simple test suite for our Cow object constructor in cow_test.js:

var expect = chai.expect;

describe("Cow", function() {
  describe("constructor", function() {
    it("should have a default name", function() {
      var cow = new Cow();
      expect(cow.name).to.equal("Anon cow");
    });

    it("should set cow's name if provided", function() {
      var cow = new Cow("Kate");
      expect(cow.name).to.equal("Kate");
    });
  });

  describe("#greets", function() {
    it("should throw if no target is passed in", function() {
      expect(function() {
        (new Cow()).greets();
      }).to.throw(Error);
    });

    it("should greet passed target", function() {
      var greetings = (new Cow("Kate")).greets("Baby");
      expect(greetings).to.equal("Kate greets Baby");
    });
  });
});

Tests should be passing, so if you open the HTML document in your browser, you should get something like:

Testing your frontend JavaScript code using mocha, chai, and sinon

If any of these expectations fails, you’ll be notified in the test results, eg. if we change the implementation of greets as below:

Cow.prototype = {
    greets: function(target) {
      if (!target)
        throw new Error("missing target");
      return this.name + " greets " + target + "!";
    }
  };<img src="http://blog.codeship.io/wp-content/uploads/2014/01/2_n1k0_cow-tests-ko.jpg" alt="Testing your frontend JavaScript code using mocha, chai, and sinon" width="885" height="382" class="alignnone size-full wp-image-670" />

You’ll get this instead:

Testing your frontend JavaScript code using mocha, chai, and sinon

How about testing asynchronous stuff?

Now imagine we implement a Cow#lateGreets method so the greetings come with a delay of one second:

Cow.prototype = {
    greets: function(target) {
      if (!target)
        throw new Error("missing target");
      return this.name + " greets " + target + "!";
    },

    lateGreets: function(target, cb) {
      setTimeout(function(self) {
        try {
          cb(null, self.greets(target));
        } catch (err) {
          cb(err);
        }
      }, 1000, this);
    }
  };

We need to test this one as well, and Mocha helps us with its optional done callback for tests:

describe("#lateGreets", function() {
    it("should pass an error if no target is passed", function(done) {
      (new Cow()).lateGreets(null, function(err, greetings) {
        expect(err).to.be.an.instanceof(Error);
        done();
      });
    });

    it("should greet passed target after one second", function(done) {
      (new Cow("Kate")).lateGreets("Baby", function(err, greetings) {<img src="http://blog.codeship.io/wp-content/uploads/2014/01/3_n1k0_cow-async-tests-ok.jpg" alt="Testing your frontend JavaScript code using mocha, chai, and sinon – timed out" width="885" height="402" class="alignnone size-full wp-image-671" />
        expect(greetings).to.equal("Kate greets Baby");
        done();
      });
    });
  });

Conveniently, Mocha will highlight any suspiciously long operation with red pills in case it wasn’t really expected:

Testing your frontend JavaScript code using mocha, chai, and sinon – timed out

Using Sinon for faking the environment

When you do unit testing, you don’t want to depend on stuff external to the unit of code under test. And while avoiding your functions to have side effects is usually a good practice, in Web development it’s not always easy task (think DOM, Ajax, native browser APIs, etc.)

Sinon is a great JavaScript library for stubbing and mocking such external dependencies and to keep control on side effects against them.

As an example, let’s imagine that our Cow#greets method wouldn’t return a string but rather directly log them onto the browser console:

// cow.js
(function(exports) {
  "use strict";

  function Cow(name) {
    this.name = name || "Anon cow";
  }
  exports.Cow = Cow;

  Cow.prototype = {
    greets: function(target) {
      if (!target)
        return console.error("missing target");
      console.log(this.name + " greets " + target);
    }
  };
})(this);

How to unit test this? Well, Sinon to the rescue! First, let’s add the Sinon script to our HTML test file:

<!-- ... -->
<script src="vendor/mocha.js"></script>
<script src="vendor/chai.js"></script>
<script src="vendor/sinon-1.7.1.js"></script>

We’ll stub the console object’s log and error methods so we can check they’re called and what’s passed to them:

var expect = chai.expect;

describe("Cow", function() {
  var sandbox;

  beforeEach(function() {
    // create a sandbox
    sandbox = sinon.sandbox.create();

    // stub some console methods
    sandbox.stub(window.console, "log");
    sandbox.stub(window.console, "error");
  });

  afterEach(function() {
    // restore the environment as it was before
    sandbox.restore();
  });

  // ...

  describe("#greets", function() {
    it("should log an error if no target is passed in", function() {
      (new Cow()).greets();

      sinon.assert.notCalled(console.log);
      sinon.assert.calledOnce(console.error);
      sinon.assert.calledWithExactly(console.error, "missing target")
    });

    it("should log greetings", function() {
      var greetings = (new Cow("Kate")).greets("Baby");

      sinon.assert.notCalled(console.error);
      sinon.assert.calledOnce(console.log);
      sinon.assert.calledWithExactly(console.log, "Kate greets Baby")
    });
  });
});

Several things to be noticed here:

  • beforeEach and afterEach are part of the Mocha API and allow to define setup and tear down operations for each test;
  • Sinon provides sandboxing, basically allowing to define and attach a set of stubs to a sandbox object you’ll be able to restore at some point;
  • When stubbed, real functions are not called at all, so here obviously nothing will be printed onto the browser console;
  • Sinon ships with its own assertion library, hence the sinon.assert calls; a sinon-chai plugin exists for Chai, you may want to have a look at it.

There are many cool other aspects of Mocha, Chai and Sinon I couldn’t cover in this blog post, but I hope it opened your appetite for investigating more about them. Happy testing!

Set up Continuous Integration and Deployment for your JavaScript projects. The Codeship supports GitHub and BitBucket projects and about any techstack you can imagine.

We want to thank Nicolas for granting us permission to republish his original blog post. If you have any questions please let us know in the comments.

Read the original blog entry...

More Stories By Manuel Weiss

I am the cofounder of Codeship – a hosted Continuous Integration and Deployment platform for web applications. On the Codeship blog we love to write about Software Testing, Continuos Integration and Deployment. Also check out our weekly screencast series 'Testing Tuesday'!

IoT & Smart Cities Stories
The platform combines the strengths of Singtel's extensive, intelligent network capabilities with Microsoft's cloud expertise to create a unique solution that sets new standards for IoT applications," said Mr Diomedes Kastanis, Head of IoT at Singtel. "Our solution provides speed, transparency and flexibility, paving the way for a more pervasive use of IoT to accelerate enterprises' digitalisation efforts. AI-powered intelligent connectivity over Microsoft Azure will be the fastest connected pat...
There are many examples of disruption in consumer space – Uber disrupting the cab industry, Airbnb disrupting the hospitality industry and so on; but have you wondered who is disrupting support and operations? AISERA helps make businesses and customers successful by offering consumer-like user experience for support and operations. We have built the world’s first AI-driven IT / HR / Cloud / Customer Support and Operations solution.
Codete accelerates their clients growth through technological expertise and experience. Codite team works with organizations to meet the challenges that digitalization presents. Their clients include digital start-ups as well as established enterprises in the IT industry. To stay competitive in a highly innovative IT industry, strong R&D departments and bold spin-off initiatives is a must. Codete Data Science and Software Architects teams help corporate clients to stay up to date with the mod...
At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...
Druva is the global leader in Cloud Data Protection and Management, delivering the industry's first data management-as-a-service solution that aggregates data from endpoints, servers and cloud applications and leverages the public cloud to offer a single pane of glass to enable data protection, governance and intelligence-dramatically increasing the availability and visibility of business critical information, while reducing the risk, cost and complexity of managing and protecting it. Druva's...
BMC has unmatched experience in IT management, supporting 92 of the Forbes Global 100, and earning recognition as an ITSM Gartner Magic Quadrant Leader for five years running. Our solutions offer speed, agility, and efficiency to tackle business challenges in the areas of service management, automation, operations, and the mainframe.
The Jevons Paradox suggests that when technological advances increase efficiency of a resource, it results in an overall increase in consumption. Writing on the increased use of coal as a result of technological improvements, 19th-century economist William Stanley Jevons found that these improvements led to the development of new ways to utilize coal. In his session at 19th Cloud Expo, Mark Thiele, Chief Strategy Officer for Apcera, compared the Jevons Paradox to modern-day enterprise IT, examin...
With 10 simultaneous tracks, keynotes, general sessions and targeted breakout classes, @CloudEXPO and DXWorldEXPO are two of the most important technology events of the year. Since its launch over eight years ago, @CloudEXPO and DXWorldEXPO have presented a rock star faculty as well as showcased hundreds of sponsors and exhibitors! In this blog post, we provide 7 tips on how, as part of our world-class faculty, you can deliver one of the most popular sessions at our events. But before reading...
DSR is a supplier of project management, consultancy services and IT solutions that increase effectiveness of a company's operations in the production sector. The company combines in-depth knowledge of international companies with expert knowledge utilising IT tools that support manufacturing and distribution processes. DSR ensures optimization and integration of internal processes which is necessary for companies to grow rapidly. The rapid growth is possible thanks, to specialized services an...
At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...