How can I check if a method has been called in a controller?

This is a question about asynchronous testing in Sails JS using Mocha.

I am writing controller validation in Sails JS using supertest . I want to check if the HTTP POST method has been called on our controller. To do this, I terminate the method and expect it to be called in the end()

following way:

request(sails.hooks.http.app)
    .post('heartbeat/create')
    .send('device: 1')
    .end(function(err, res) {
        expect(publishCreateStub.called).to.be.true;
        done();
    });

      

When I run this, the wait fails because the method is not called on assertion. But when I put the wait in the setTimeout

following way, it works:

request(sails.hooks.http.app)
    .post('heartbeat/create')
    .send('device: 1')
    .end(function(err, res) {
        setTimeOut(function() {
            expect(publishCreateStub.called).to.be.true;
            done();
        }, 1000);
    });

      

Is there a way to do a test pass without setTimeout

?

Here is the piece of code I'm testing: HeartbeatController # create

You can also help us resolve the issue by submitting transfer requests: https://github.com/multunus/one-mdm/issues/1

+3


source to share


2 answers


The actual problem is that your controller is not waiting for a call publishCreate

. So you answer 201 Created

without checking that something is created.

There is a chance that either Heartbeat.findOne

or Heartbeat.publishCreate

will fail, but you will learn more about it.

To fix this problem, you must change your controller by moving part of the response inside the Heartbeat

callbackback:

create: function (req, res, next) {
  if(req.isSocket) {
    Heartbeat.watch(req);
  }

  Heartbeat.create(req.body)
    .exec(function(error, heartbeat) {
      if(error) {
        res.status(422);
        return res.send('Invalid heartbeat data');
      }

      Heartbeat.findOne(heartbeat.id).populate('device').then(function(newHeartbeat) {
        Heartbeat.publishCreate(newHeartbeat.device);
      }).then(function() {
        // success
        res.status(201);
        res.json({
          device: heartbeat.device
        });
      }, function(err) {
        // something bad happened
        next(err);
       });

    });
}

      



In my example, I delegate the actual error handling to the following error handling middleware:

next(err);

      

But you can solve the problem yourself yourself, for example:

res.status(400);
res.send('Can not publish Heartbeat create');

      

+3


source


Your problem is not your test, this is your application:

Heartbeat.create(req.body)
  .exec(function(error, heartbeat) {
    if(error) {
      res.status(422);
      return res.send('Invalid heartbeat data');
    }        

    Heartbeat.findOne(heartbeat.id).populate('device').then(function(newHeartbeat) {
      Heartbeat.publishCreate(newHeartbeat.device);
    });

    res.status(201);
    return res.json({
      device: heartbeat.device
    });
  });
}

      

https://github.com/multunus/one-mdm/blob/master/api/controllers/HeartbeatController.js



This is where you create a promise, but you don't return that promise. Thus, you cannot wait for a promise (or error) to be fulfilled before continuing.

Without knowing your application in more detail, I'm not sure about the fix. I would look at abstracting the bits in the middle to the service *, then your test HeartbeatController

can just check if the service is called (synchronously) and your test for the service can do the part of the asynchronous usage that uses the promise.

* Update: "service", I just want to distract him from any HTTP issues, so he doesn't know about the request or response objects. The end result is that you've decoupled the HTTP (controller) part from the heartbeat part. This simplifies both parts of the unit test.

+1


source







All Articles