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
source to share
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');
source to share
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.
source to share