TNG/sinon-helpers

Any good way to stub nested methods?

starmandeluxe opened this issue · 2 comments

First of all, great library, this is simplifying my code since I'm using dependencies that require constructors.

However I am still not sure how to stub something like @google-cloud/storage where you have things like a library function returning an object with yet another function that I need to stub out, e.g.:

const Storage = require('@google-cloud/storage');
const storage = new Storage();
storage
  .bucket(bucketName)
  .getFiles()
  .then(results => {
    // blah blah
  });

So as you can see, bucket is easily stubbed, however how would I stub one level deeper (getFiles())?

So far what I came up with in my unit test is this (ugly, but works):

const Storage = require('@google-cloud/storage');
const sh = require('sinon-helpers');

const storageStub = sh.getStubConstructor(Storage);
storageStub = storageStub.withMethods('bucket', sh.returning({ 
  getFiles : sinon.stub().returns({ 
    // blah blah blah
  }) 
}));

The above idea comes from this post: https://groups.google.com/d/msg/sinonjs/W_CclBb7JU8/JSGOmehzFbcJ

FYI I am also using proxyquire to replace the required npm module later on in the unit test.

Is there a better way to do this!?

Not really a great improvement but I find .afterCreation to have generally a more straightforward API which also gives you more control over what your instances can do (including different instances doing different things). Also, you do not need to use sinon.stub() unless you specifically want to query the nested stubs on your instances if/how they have been called:

const Storage = require('@google-cloud/storage');
const sh = require('sinon-helpers');

const storageStub = sh.getStubConstructor(Storage)
  .afterCreation(instance => {
    instance.bucket.returns({getFiles: () => ({...})});
  });

I actually consider deprecating some of the other APIs in favor of the .afterCreation API (and maybe find a better, shorter name for it?). Suggestions are always welcome.

Thanks for the information. It doesn't look much different except syntactically, but I will try this out.

Yes, I would need to assert that the nested functions were called as well, so I assume I do need to use sinon for the "sub-function" (however maybe I shouldn't be anonymously calling sinon.stub() inside there since I won't be able to reference it later for the assert, so I'll try supplying a pre-created stub instead).

This all gets really hairy when you have lots of nested functions in some of these libraries such as:

storage
    .bucket(bucketName)
    .file(srcFilename)
    .download(options)
    .then(() => { 
      //blah 
    });

In these cases, it starts feeling less like a unit test and somewhat like you're re-implementing external libraries...