Mocha vs Jasmine, Chai, Sinon & Cucumber in 2024

| 8 min. (1515 words)

Javascript has been enabling browsers for years, and for better or for worse, the internet is made of JS. NodeJS brought it to the server side. TypeScript has wrapped familiar object-oriented, statically-typed syntax around it. Anywhere you look, you’ll find Javascript: on the client, server, mobile, and embedded systems.

All good software must be tested, and software written in Javascript is no exception (pun intended.) The JS ecosystem is huge, so there are a LOT of Javascript test frameworks are available. They variously offer features like:

  • Test runners
  • BDD-style test suites
  • Assertions
  • Spies
  • Fakes

Mocha vs Jasmine

Let’s take a look at two of the most popular test frameworks, Jasmine and Mocha, with an eye toward their similarities and differences.

Jasmine: Created around 2008. The documentation describes Jasmine as “batteries included,” meaning that it attempts to provide everything a developer needs in a test framework.

Mocha: Younger than Jasmine, created around 2011. Mocha is not a “complete” test framework, and doesn’t attempt to be. Instead, Mocha covers the basics and allows developers to extend it with other frameworks.

Common Ground: BDD Tests

At their core, Mocha and Jasmine both offer Behavior-Driven Design (BDD) tests:


View Codepen

describe("My First Test Suite", function() {
  it("introduces a test suite", function() {
    expect(true).toBe(true);
  });
});

The following functions are what Mocha and Jasmine have in common. The describe function defines a test suite. The it function implements the BDD method of describing the behavior that a function should exhibit. The first parameter is a plain-English description of the behavior under test. The second parameter is a function that will call assertions that will pass or fail, depending on whether the described behavior was, or was not confirmed. A single describe test suite can embed multiple calls to the it function.

The implementations of Mocha and Jasmine diverge beyond this point. Now let’s take a look at their differences.

Mocha and Jasmine Differences

Assertions

Assertions are boolean functions that are used to test behavior. Typically, a true result indicates that a test passed, indicating that the expected behavior is what occurred when the test ran.

Jasmine includes an assertion library that uses an expect-style syntax:


View Codepen

describe("Spying on an object", function() {
  var car, go = null;

  //the beforeEach setup function will be called with each run of the suite
  beforeEach(function() {
    car = {
      go: function(value) {
        speed = value;
      }
    };
    spyOn(car, 'go'); //creates the spy

    car.go(50);
    car.go(80);
  });

  //This test will succeed
  it("Called the go function", function() {
    expect(car.go).toHaveBeenCalled();
  });

  //This test will fail- the go function is called twice
  it("Called the go function once", function() {
    expect(car.go).toHaveBeenCalledTimes(1);
  });
});

Mocha does not have a built-in assertion library. Developers must use a dedicated assertion library in combination with Mocha. The popular Chai assertion library uses a syntax similar to Jasmine:


View Codepen

describe("My First Test Suite", function() {
  it("introduces a test suite", function() {
    expect(true).to.equal(true);
  });
});

Chai uses a “fluent” syntax, where comparison operators can be chained together:

expect(foo).to.equal('foo')	     	//equality
expect(foo).to.not.equal('foo')	    //inequality
expect(foo).to.be.a('string')   	//type assertion

Chai also supports should-style and “classical” assert assertions.

Test Doubles / Spies

Test double frameworks create test doubles. A test double, or spy, is like a clone of an object. A test double has the same functions as the original object. However, those functions are “stubbed out,” meaning that they don’t do anything. The “stubbed” functions exist so the test double framework can “watch” the double, tracking the calls to its functions.

Jasmine includes a spyOn method that can be used to create spies:


View Codepen

describe("Spying on an object", function() {
  var car, go = null;

  //the beforeEach setup function will be called with each run of the suite
  beforeEach(function() {
    car = {
      go: function(value) {
        speed = value;
      }
    };
    spyOn(car, 'go'); //creates the spy

    car.go(50);
    car.go(80);
  });

  //This test will succeed
  it("Called the go function", function() {
    expect(car.go).toHaveBeenCalled();
  });

  //This test will fail- the go function is called twice
  it("Called the go function once", function() {
    expect(car.go).toHaveBeenCalledTimes(1);
  });
});

Once again, Mocha does not include a spy framework. The SinonJS framework is a popular choice for creating spies:


View Codepen

describe("Spying on an object", function() {
  var car, go = null;

  //the beforeEach setup function will be called with each run of the suite
  beforeEach(function() {
    car = {
      go = function(value) {
        speed = value;
      }
    };
    sinon.spy(car, 'go'); //creates the spy

    car.go(50);
    car.go(80);
  });

  //This test will succeed
  it("Called the go function", function() {
    assert(car.go.called);
  });

  //This test will fail- the go function is called twice
  it("Called the go function once", function() {
    assert(car.go.calledOnce);
  });
});

The overall syntax between Jasmine and SinonJS are similar, but there is a key difference. The Jasmine spy will not call the original “go” function by default. Only the spy “stub” is called unless .and.callThrough() is used when creating the spy:

spyOn(car, 'go').and.callThrough()

Sinon, however, will “pass through” the call to the implementation of the spied-on function by default.

Fake Server

Test “fakes” are used to isolate a test from external dependencies. Unit tests should not make calls outside their scope to networks or databases. Fakes are used to simulate that behavior without actually making any external calls.

SinonJS is a complete framework test double framework than Jasmine, including not only spies but also stubs and fakes. Here is an example of a test using the SinonJS fake server:


View Codepen

before(function() {
  server = sinon.fakeServer.create();
});
after(function() {
  server.restore();
});

it("calls callback with deserialized data", function() {
  var callback = sinon.spy();
  getCustomer(5, callback);

  // This is part of the FakeXMLHttpRequest API
  server.requests[0].respond(
    200, {
      "Content-Type": "application/json"
    },
    JSON.stringify([{
      id: 5,
      FirstName: "Bob",
      LastName: "Dobbs"
    }])
  );

  assert(callback.calledOnce);
});

Jasmine does not include fakes, so the Mocha + SinonJS option is best when you want to fake an HTTP server.

Supporting Test Frameworks

ChaiJS

Assertions are at the core of all testing. An assertion says “this should be true,” and can apply to any statement. Test-Driven Development relies on assertions as the basis for unit tests. Unit tests assert that a condition is true; by definition, if the condition is false, the test fails. This could be as simple as an expected return value from a function, or as complex as the expected order of a sequence of function calls.

The ChaiJS library provides three styles of assertion:

Chai styles of assertion

These three styles, Should, Expect, and Assert, are all functionally identical. They are all equally capable of expressing any assertion, though not all styles work equally well in all browsers.

Each style has a history- “Expect” uses a style from Behavior-Driven Development.“Should” uses a similar chained format, and is different only in style. “Assert” is a more “classical” format rooted in traditional TDD. Developers tend to choose the style with which they are most familiar.

ChaiJS can be combined with any other BDD or testing framework to support assertions.

SinonJS

As discussed earlier, tests should be isolated from dependencies. Tests are supposed to be fast, and test logic only. No test, unit or behavioral, should ever make a call over a network, to a disk, or a database. How, then, does one test a function that calls a service, or queries a database?

Sinon JS engine test

Fakes, Mocks, and Stubs are objects that are patterned from real objects but exist only to simulate communication between a function and its external dependencies. SinonJS is a stand-alone library that works with any test framework. Its syntax is simple and provides powerful features. SinonJS can be used to test functions in a completely isolated state, and to write assertions about a function’s interaction with fakes, stubs and mocks created by SinonJS.

Cucumber

Behavior-Driven Development is a collection of tools and techniques that originated in Test-Driven Development. The key difference is that TDD is used for unit testing, whereas BDD is designed to assert high-level behaviors. The BDD assertion style uses plain-English sentences that can be understood by non-technical resources, such as Business Analysts and Product Owners.

Cucumber is a well-established BDD testing framework. It is compatible with Javascript, Ruby, and Java. BDD is a powerful technique for expressing and asserting behaviors at the process level. Cucumber is a powerful library used to implement BDD.

Conclusion

Jasmine comes included with assertions and spies, but that doesn’t necessarily make it the better option. Mocha does one thing and does it well: BDD test definitions. Other best-of-breed frameworks combine with Mocha to extend it.

The best way to make your choice between Mocha and Jasmine is to try both. Your particular needs, and the nature of the application under test will make the better choice clear as you experiment. You can combine them, but to keep your tests consistent and readable, I recommend using Jasmine or Mocha plus Chai plus Sinon, within an application.

Good luck!