0
votes

I'm having trouble tracking down why a sinon spy isn't being triggered. In the test below both console statements report as false so neither method is being called (in case there was an error).

This is what one of my mocha tests generally looks like:

describe('Post Controller', function () {
    var controller = require('../controllers/PostController'),
    req = {
        user: {
            check_id: "00100",
            access_id: "54876329"
        }
    };

beforeEach(function () {
    res = {
        send: sinon.spy(),
        json: sinon.spy()
    };
});

afterEach(function () {
    res.send.reset();

});

describe('readAllPosts', function () {
    it('should return an array of posts', function (done) {
        controller.readAllPosts(req, res);
        process.nextTick(function () {
            console.log('send: ', res.send.called);
            console.log('json: ', res.json.called);
            assert(res.json.calledWith(sinon.match.array));
            done();
        });
    });
});

});

The readAllPosts method in PostContoller:

var postController = {
    readAllPosts: function (req, res) {
        PostModel.find(
            {
                access_id: req.user.access_id
            },
            function (err, results) {
                if (err) {
                    res.send(404, err);
                } else {
                    // res here is a spy
                    res.json(results);
                }
            }
        );
    }
};

And finally the find method in PostModel:

Posts.find= function (conditions, cb) {
    /* ... */
    dbQuery(query, function (err, results) {
        if (err) {
            cb(err);
        }

        cb(null, results);
    });
};

If I call the methods in normal fashion they execute find, returning the expected array of Posts. However the res spy is never executed. Also, if I change the method in the controller class to this

var postController = {
    readAllPosts: function (req, res) {
        res.json([{this:"that",and:"other"}]);
    }
};

the spied function (res) does fire. I know that the callback sent to the Model is being called and that the res object is there but it's not called. I don't think this is an indirection issue (calling the original method rather than the sinon wrapped one) but I'm not sure where the issue lies.

1

1 Answers

0
votes

Testing async functions with Sinon spies/stubs is difficult, because (AFAIK) you can't get one to tell you when it got called (it might even never get called at all).

In your case, you're assuming that PostModel.find() will complete in the next tick (judging by your use of process.nextTick()), which most likely isn't the case, so you're checking your spies when the query is still running and none of the spies would have been called yet.

It also seems to me that you're trying to test too much with one test: PostModel.find(), dbQuery(), and controller.readAllPosts(). I would probably split those up in different tests, and stub the rest.

So for instance, if you want to check if your controller sends back the correct response, or if it handles errors properly, stub PostModel.find() to have it call the callback (synchronously) with either an error or a proper result array, and subsequently check the res.send/res.json spies.

Another option would be to not use spies, but plain callbacks. For instance:

controller.readAllPosts(req, {
  send : function(data) {
    assert(data, ...);
    done();
  },
  json : function(data) {
    assert(data, ...);
    done();
  },
});

(or variations thereof)

However, I prefer not to do this because it prevents you from testing (in an easy way) that not both of those methods got called, for instance.