Search code examples
javascriptnode.jsunit-testingsinonstub

How can I stub a Node.js MongoDB chained fn call to mock my end result?


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?


Solution

  • 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:

    Stubbing toArray() using callback

    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();
    
    });
    

    Stubbing toArray() using promise

    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.