I am trying to test (with Sinon.JS Stubs) the following call with the Node.js MongoDB driver...
collection.find({mood: 'happy'}).toArray((err, result) => {
// array result
cb(null, result);
});
What is hanging me up here is the .toArray()
chained function. The above call returns result
as the expected Array
.
To demonstrate my struggle - and to contrast - I am able to stub the following call which is not chained as such...
collection.findOne({id: 'id'}, (err, result) => {
// single result
cb(null, result);
});
stubb'd =>
findOneStub.yields(null, {username: 'craig'});
Is there a straightforward way to stub my .find
call, which returns a function with .toArray
on it to finally mock my results?
What I usually do when I want to stub methods of complex nested object like with the Mongo driver case, is to mock an object that mimics the call chain like so:
let mockDbCollection = {
find: function() {
return {
toArray: function(cb) {
const results = [{
username: 'craig'
}];
cb(null, results);
}
};
}
};
sinon.stub(db, 'collection')
.returns(mockDbCollection);
db.collection('users').find({
id: 'id'
}).toArray((err, docs) => {
console.log(docs);
done();
});
let mockDbCollection = {
find: function() {
return {
toArray: function() {
return Promise.resolve([{
username: 'craig'
}]);
}
};
}
};
sinon.stub(db, 'collection')
.returns(mockDbCollection);
db.collection('messages').find({
id: 'id'
}).toArray().then((docs) => {
console.log(docs);
done();
});
This paradigm can be used in any case that you want to mock a sequence of calls on a complex object, focusing only on the response of the last call in the chain. You can go as deep as you want without any issues.
If you want something more advanced like setting behavior of the stub or counting calls, etc you can find out some other techniques in this article. The author showcases some examples using complex DOM objects.
Adapting the technique from the tutorial in our example, we could easily do the following:
// here we stub the db.collection().findOne() method
// and fabricate a response
let stubFindOne = this.sandbox.stub().resolves({
_id: 'id'
});
// now, we can set the stub
// function in our mock object
let mockDb = {
collection: () => {
return {
findOne: stubFindOne
};
}
};
This way, we can manipulate and inspect the stubbed method as we would normally do, e.g.
const args = stubFindOne.firstCall.args;
would return the arguments list of the first call, etc.