Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test a function which calls another that returns a promise?

I have a node.js app using express 4 and this is my controller:

var service = require('./category.service');

module.exports = {
  findAll: (request, response) => {
    service.findAll().then((categories) => {
      response.status(200).send(categories);
    }, (error) => {
      response.status(error.statusCode || 500).json(error);
    });
  }
};

It calls my service which returns a promise. Everything works but I am having trouble when trying to unit test it.

Basically, I would like to make sure that based on what my service returns, I flush the response with the right status code and body.

So with mocha and sinon it looks something like:

it('Should call service to find all the categories', (done) => {
    // Arrange
    var expectedCategories = ['foo', 'bar'];

    var findAllStub = sandbox.stub(service, 'findAll');
    findAllStub.resolves(expectedCategories);

    var response = {
       status: () => { return response; },
       send: () => {}
    };
    sandbox.spy(response, 'status');
    sandbox.spy(response, 'send');

    // Act
    controller.findAll({}, response);

    // Assert
    expect(findAllStub.called).to.be.ok;
    expect(findAllStub.callCount).to.equal(1);
    expect(response.status).to.be.calledWith(200); // not working
    expect(response.send).to.be.called; // not working
    done();
});

I have tested my similar scenarios when the function I am testing returns itself a promise since I can hook my assertions in the then.

I also have tried to wrap controller.findAll with a Promise and resolve it from the response.send but it didn't work neither.

like image 828
jbernal Avatar asked Aug 27 '16 11:08

jbernal


People also ask

How to test async function in Jasmine?

You can check on the spied on function in . then of the async call. This is where you can use toHaveBeenCalled or toHaveBeenCalledWith to see if it was called. You should also check if the result of the promise is the expected output you want to see via the toEqual matcher.

What is promise in testing?

The Promise is an Object introduced in ECMA Script 2015 as part of 6th Edition. It represents the future completion of Success / Failure Operation. It mainly helps to works smoothly with asynchronous operations. Promise API is mainly followed Promises/A+ Specification.

How to test promise in JavaScript?

For example: it('should fail the test', function() { var p = Promise. reject('this promise will always be rejected'); return p; }); The above test returns a rejected promise, which means that it fails every time.

How do I test API calls in Jasmine?

Create a Mock API with Jasminejson file in your root directory. If not, you can set it up with this command: npm init -y . Your test should pass! Try changing the value of a to false, and running it again to see what it looks like when a test fails.


2 Answers

You should move your assert section into the res.send method to make sure all async tasks are done before the assertions:

var response = {
   status: () => { return response; },
   send: () => {
     try {
       // Assert
       expect(findAllStub.called).to.be.ok;
       expect(findAllStub.callCount).to.equal(1);
       expect(response.status).to.be.calledWith(200); // not working
       // expect(response.send).to.be.called; // not needed anymore
       done();
     } catch (err) {
       done(err);
     }
   },
};
like image 92
Johannes Merz Avatar answered Oct 03 '22 21:10

Johannes Merz


The idea here is to have the promise which service.findAll() returns accessible inside the test's code without calling the service. As far as I can see sinon-as-promised which you probably use does not allow to do so. So I just used a native Promise (hope your node version is not too old for it).

const aPromise = Promise.resolve(expectedCategories); 
var findAllStub = sandbox.stub(service, 'findAll');
findAllStub.returns(aPromise);

// response = { .... }

controller.findAll({}, response);

aPromise.then(() => {
    expect(response.status).to.be.calledWith(200);
    expect(response.send).to.be.called;    
});
like image 42
Viktor Molokostov Avatar answered Oct 03 '22 21:10

Viktor Molokostov