Mocha vs Jasmine, Chai, Sinon & Cucumber in 2024
Posted Dec 5, 2023 | 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
Raygun lets you detect and diagnose errors and performance issues in your codebase with ease
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:
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:
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:
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:
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:
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:
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:
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?
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!