神刀安全网

Keep Calm and Love JavaScript Unit Tests – Part 2: Asynchronism

In the previousarticle we discovered how to write simple unit tests thanks to the Mocha-Sinon-Chai stack. Let’s now continue with this stack and focus on a problem we will necessarily be confronted to if we use JavaScript: testing asynchronous code.

A full working example of the code snippets shown below can be found in this repository: https://github.com/tib-tib/demo-js-tests-part-2 . There are several ways to do asynchronous JavaScript. I focused on callbacks and promises .

How to test an asynchronous function with callback?

To begin with, let’s take a simple example of a function retrieving a list of superheroes:

var SuperheroModel = require('./SuperheroModel'); var logger = require('./logger');  module.exports = {   getSuperheroesList: function (callback) {     SuperheroModel.find(function (error, superheroes) {       if(error) {         logger.error(error);       }       return callback(error, superheroes);     });   } }; 

Here, we have an asynchronous call to a find method of a superhero model.

We want to test two things:

  • if there is an error, check that it is logged and returned.
  • if all went well, check that the getSuperheroesList function returns the superheroes list.

To achieve this, we need to create astub of our find method. However, we cannot use the returns function of the stub as we do usually, because the find method here returns its results via its callback.

In fact, we have to tell the stub to call the callback function with the specific values we want. To do so, Sinon provides us a callsArgWith function. This function takes as parameters the argument index to which we will pass values, and the values to pass.

Let’s see how it goes:

var sinon = require('sinon'); var sinonChai = require('sinon-chai'); var chai = require('chai'); var should = chai.should(); chai.use(sinonChai);  var superheroService = require('./superheroService'); var SuperheroModel = require('./SuperheroModel'); var logger = require('./logger');  var sandbox; var findStub;  describe('superheroService', function() {   beforeEach(function() {     sandbox = sinon.sandbox.create();     findStub = sandbox.stub(SuperheroModel, 'find');     sandbox.stub(logger, 'error');   });    afterEach(function() {     sandbox.restore();   });    it('should return a list of superheroes', function() {     var superheroesList = ['Batman', 'Superman', 'Iron Man'];     findStub.callsArgWith(0, null, superheroesList);      superheroService.getSuperheroesList(function (error, result) {       should.not.exist(error);       result.should.deep.equal(superheroesList);     });   });    it('should log and return an error', function() {     findStub.callsArgWith(0, 'A_BIG_ERROR');      superheroService.getSuperheroesList(function (error, result) {       error.should.equal('A_BIG_ERROR');       should.not.exist(result);       logger.error.should.have.been.calledWithExactly('A_BIG_ERROR');     });   }); })  

Thereby, in the first test we specify with a findStub that the callback of the find method has to be called with no error and a fake superheroes list, whereas in the second one we tell to call it with an error.

You can notice the presence of sinon-chai , that extends Chai with custom assertions such as calledWithExactly .

We also used a sandbox in the beforeEach and afterEach functions.

It allows us to restore all the stubs defined between each test of the suite.

Now let’s take a look at the same example with a promise.

How to test an asynchronous function with promise?

Now assume the getSuperheroesList function looks like this:

var SuperheroModel = require('./SuperheroModel'); var logger = require('./logger');  module.exports = {   getSuperheroesList: function () {     return SuperheroModel.find()     .then(function (superheroes) {       return superheroes;     })     .catch(function (error) {       logger.error(error);       throw error;     });   } };  

In the same way as above, we want to test the two situations (when the promise is resolved and when it is rejected):

var sinon = require('sinon'); var sinonChai = require('sinon-chai'); var chai = require('chai'); var should = chai.should(); chai.use(sinonChai);  var superheroService = require('./superheroService'); var SuperheroModel = require('./SuperheroModel'); var logger = require('./logger');  var sandbox; var findStub;  describe('superheroService', function() {   beforeEach(function() {     sandbox = sinon.sandbox.create();     findStub = sandbox.stub(SuperheroModel, 'find');     sandbox.stub(logger, 'error');   });    afterEach(function() {     sandbox.restore();   });    it('should return a list of superheroes', function() {     var superheroesList = ['Batman', 'Superman', 'Iron Man'];     findStub.returns(new Promise(function(resolve) {       resolve(superheroesList);     }));      return superheroService.getSuperheroesList()     .then(function(result) {       result.should.deep.equal(superheroesList);     });    });    it('should log and return an error', function() {     findStub.returns(new Promise(function(resolve, reject) {       reject('A_BIG_ERROR');     }));      return superheroService.getSuperheroesList()     .catch(function(error) {       error.should.equal('A_BIG_ERROR');       logger.error.should.have.been.calledWithExactly('A_BIG_ERROR');     });   }); }) 

The findStub now returns in the first test a resolved promise and in the second one a rejected promise.

You can simplify your promises tests by using the Chai as Promised library. It allows you to use directly assertions on your promises instead of writing the promise handlers manually. You can see the same example as above with this library in my repository .

Now that you know the basics about asynchronous testing in JavaScript, you have no more excuses to not unit testing your code!

You liked this article? You’d probably be a good match for our ever-growing tech team at Theodo.

Join Us

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Keep Calm and Love JavaScript Unit Tests – Part 2: Asynchronism

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址