mercredi 23 septembre 2015

Node.js - Unit testing a controller with tape and simon.js. Unable to test the result with a spy.

I am failing at trying to validate that my controller (get method) is doing what it should under different inputs. In this case, I am trying to spy the res.json method in order to check it is called once when I pass the required inputs to the controller.

This is my broadcast controller (I am testing the get method):

'use strict';
module.exports = function(models) {

  var controller = {
    get: get
  };

  function get(req, res, next) {
    models.Broadcast.findById(req.params.id)
      .then(function(broadcast) {
        console.log(res);
        res.json(broadcast);
      })
      .catch(function(err) {
        next(err);
      });
  }
  return controller;
};

As you can see, It receive a models object which contain a Broadcast object that expose the findById method (this method query the DB to get a broadcast instance with the id equal to req.params.id).

So, using tape and simon packages, I created the next test:

'use strict';

var test = require('tape');
var sinon  = require('sinon');

test('Broadcast controller.get', function(assert) {

  // Create a fake broadcast object
  var broadcast = {
    id: 1,
    message: 'dummy',
    popularity: 0
  };

  // Mock the models object
  var models = {};
  models.Broadcast = {};

  // Create a stub of the findById object (it should return a promise)
  models.Broadcast.findById = sinon.stub().returns(Promise.resolve(broadcast));

  // Require the broadcast controller passing the models object
  var broadcastCtlr = rootRequire('controllers/api/broadcasts/broadcasts')(models);

  // Mock the req, res and next parameters (used by the get method=
  var req, res, next;
  req = {
    params: {
      id: 1
    }
  };
  res = {};

  // Create a stub for the json method (this one must be called once)
  res.json = sinon.stub().returns(broadcast);

  // Create a spy on the next (I will use this to test the error case)
  next = sinon.spy();

  // A basic assertion
  assert.ok(broadcastCtlr.get, 'the controller was successfully loaded');

  // Execute the controller
  broadcastCtlr.get(req, res, next);

  // Assert that the findById method was correctly called 
  assert.ok(models.Broadcast.findById.calledOnce, 'findById called once');
  assert.ok(models.Broadcast.findById.calledWith(1), 'findById called with id = 1');

  // Assert that the res.json method was correctly called (this is not working)
  assert.ok(res.json.calledOnce, 'the json response was sent');
  assert.ok(res.json.calledWith(broadcast), 'controller must return res.json(broadcast)');

  // Assert that the next method was not called (this is ok)
  assert.notOk(next.calledOnce, 'next should not be called (no error)');
  assert.end();

});

After running this test, the assertions about the res.json method failed and I can't understand why they are failing.

This is the output:

# Broadcast controller.get
ok 1 the controller was successfully loaded
ok 2 findById called once
ok 3 findById called with id = 1
not ok 4 the json response was sent
  ---
    operator: ok
    expected: true
    actual:   false
    at: Test.<anonymous> (/Users/rcanepa/Development/colossus/lifePartner/express-version/test/unit/controllers/broadcasts.js:51:10)
  ...
not ok 5 controller must return res.json(broadcast)
  ---
    operator: ok
    expected: true
    actual:   false
    at: Test.<anonymous> (/Users/rcanepa/Development/colossus/lifePartner/express-version/test/unit/controllers/broadcasts.js:52:10)
  ...
ok 6 next should not be called (no error)

1..6
# tests 6
# pass  4
# fail  2

Finally, something weird I noticed is that if I test the execution of the res.json method (console.log(res.json.calledOnce)) inside the controller, it returns true. Why would it return true in my controller but not in the test's scope?

I probed this adding this line to my get method:

...

.then(function (broadcast)
    res.json(broadcast);
    console.log(res.json.calledOnce); // return true!!!
  })

...

Aucun commentaire:

Enregistrer un commentaire