Testing in JS is becoming an increasingly common practice. But where to start? There are many frameworks that provide an API for writing JS tests.
This article is a brief overview of the differences between two popular JS testing frameworks: Jasmine 2 and Mocha. We will also discuss the most common Chai and Sinon libraries that are often used in conjunction with Jasmine and Mocha.
1. API (application programming interface)
The APIs in Jasmine and Mocha are very similar. They both support writing tests using the BDD (Behavior Driven Development) approach. You may ask: “what is BDD”? In short, this is an approach to writing tests that provides the ability to describe the functionality in a spoken language.
')
describe('calculator', function() { describe('add()', function() { it('should add 2 numbers together', function() {
Assertions, or expectations, as they are often called, differ in the frameworks presented. Mocha does not have a built-in
assertion library. There are several options for use in the Node.js environment for browsers: Chai, should.js, expect.js, and better-assert. Most developers choose Chai as the
assertion library. Since none of the
assertion libraries are included with Mocha, you will need to plug it into your test environment. In Chai, there are three types of
assertions :
1) should
2) expect
3) assert (approve)
The type of
expect is similar to
expect which provides us with the Jasmine framework. For example, if you want to write a check of the add method and your expectation that calculator.add (1, 4) will be 5, then this check will look similar to using both Jasmine and Chai.
Very similar, right? If you move from Jasmine to Mocha, the path is pretty simple - use Chai with the type of
expect .
2. Test Doubles (Understudies)
Test Doubles replace one object for another for test purposes. In Jasmine, the role of
test doubles is performed by
spies (spies).
Spy is a function that replaces the original function, the logic of which we want to change and describes how this function will be used as part of the test.
Spies allow:
1) Find out the number of times the
spy function was called
2) Specify the return value in order for the code under test to continue to work according to the desired algorithm.
3) Indicate that the
spy function throws an error.
4) Find out with which arguments the
spy function was called.
5) Indicate that the
spy function calls the original function. (Which she replaced)
In Jasmine, creating a
spy for an existing method is possible like this:
var userSaveSpy = spyOn(User.prototype, 'save');
It is also possible to create a
spy , even if you do not have a method that you want to replace.
var spy = jasmine.createSpy();
Mocha does not have a built-in
test doubles library so you need to download and connect
Sinon to your test environment.
Sinon is a very powerful
Test Doubles library which provides equivalent
Jiesmine spies functionality and a bit more. It is worth noting that
Sinon breaks
test doubles into three different categories:
spies ,
stubs and
mocks , between which there are subtle differences.
The spy function in
Simon is invoked using the original method, while in Jasmine this behavior must be specified. For example:
spyOn(user, 'isValid').andCallThrough()
In this example, the original user.isValid will be called.
The next type of
test doubles is
stubs which replaces the original method. The
stubs behavior is similar to the default
spies behavior in Jasmine, in which the original method is not called.
sinon.stub(user, 'isValid').returns(true)
In this example, if the user.isValid method is called, the original user.isValid method will not be called, but its fake version will be called which should return the result “true”.
From your experience,
spies in Jasmine cover almost everything that is required for
test doubles , so in most cases you will not need
Sinon if you use Jasmine, however, if you want, you have the opportunity to use them together. The main reason I use
Sinon with Jasmine is its
fake server .
3. Asynchronous tests (Asynchronous Tests)
Asynchronous testing in Jasmine 2.x and Mocha is implemented similarly.
it('should resolve with the User object', function(done) { var dfd = new $.Deferred(); var promise = dfd.promise(); var stub = sinon.stub(User.prototype, 'fetch').returns(promise); dfd.resolve({ name: 'David' }); User.get().then(function(user) { expect(user instanceof User).toBe(true); done(); }); });
In this example, User is a constructor function that has a static get method. The get method internally uses the fetch method which executes the XHR request. I want to check that when the get method gets a value, this value will be an instance of User. Since I used the “stub” for the User.prototype.fetch method and told it to return a predefined
promise , the real XHR request is not executed. Coverage of this code continues to be asynchronous.
It is very simple to indicate that the
callback function in the it construct is waiting for an argument (in this case done) and the
test runner will wait until the function is executed before it finishes the test. The test will be suspended and an error will be displayed if the argument is not called within a certain time. This gives you complete control over when tests complete. The test written above will work in both Jasmine and Mocha.
If you work with Jasmine 1.3, asynchronous testing does not look so “clean”.
An example of asynchronous testing in Jasmine 1.3:
it('should resolve with the User object', function() { var flag = false; var david; runs(function() { var dfd = new $.Deferred(); var promise = dfd.promise(); dfd.resolve({ name: 'David' }); spyOn(User.prototype, 'fetch').andReturn(promise); User.get().then(function(user) { flag = true; david = user; }); }); waitsFor(function() { return flag; }, 'get should resolve with the model', 500); runs(function() { expect(david instanceof User).toBe(true); }); });
In this example, the test will wait for a maximum of 500 milliseconds to complete an asynchronous operation; otherwise, the test will fail. The waitsFor () function constantly checks the flag as soon as the flag becomes true, execution will continue and the next runs block will be called.
4. Sinon Fake Server
One feature that
Sinon has in comparison with Jasmine is a
fake server (fake server). This allows you to set fake responses to certain AJAX requests.
it('should return a collection object containing all users', function(done) { var server = sinon.fakeServer.create(); server.respondWith("GET", "/users", [ 200, { "Content-Type": "application/json" }, '[{ "id": 1, "name": "Gwen" }, { "id": 2, "name": "John" }]' ]); Users.all().done(function(collection) { expect(collection.toJSON()).to.eql([ { id: 1, name: "Gwen" }, { id: 2, name: "John" } ]); done(); }); server.respond(); server.restore(); });
In this example, sending a GET request to / users, we get a response 200, containing data about two users - Gwen and John. This can be very convenient for several reasons. First, you can test the code that makes AJAX requests, no matter which AJAX library you use. Second, you can test the function that makes the AJAX request and pre-processes the returned data before fulfilling the promise (
resolve promise ). Thirdly, there is an opportunity to announce several answers to a successful request. For example, in the following cases: successful withdrawal of funds from a credit card, outdated card, incorrect CVV code, etc. different answers will come. If you worked with AngularJs, then
Sinon Fake Server is similar to the $ httpBackend service.
Total:
Jasmine framework includes almost everything that is needed, including
assertions ,
test doubles (implemented through
spies ) functionality. Mocha does not have this functionality, but offers a choice of libraries for
assertions (the most popular
Chai ). For
test doubles, Mocha also requires connecting an additional library, in most cases, this is sinon.js.
Sinon can also be a great addition by providing your
fake server (fake server).
Choosing a test framework for JS can be a difficult task, I hope that this article helped you make the right choice. In any case, whatever you use, you can’t go wrong. Have a good test!
Original article