1

I'm trying to stub a function using sinon. The function has the following signature

export function getIndexDocument(
    svc: MetaHTTPService | ServiceConfig
): MetaPromise<RepoResponseResult<IndexDocument>> {

Is this the right way to sub it

sandbox.stub(getIndexDocument).resolves({} as RepoResponseResult)

I tried that but it returns an error.

Here's how this function is called.

I have a class called AssetsController with the following functions

 public async exploreIndexDocument(): Promise<Asset | undefined> {      
    // it makes an HTTP request and returns a promise that resolves with the following info { repoId: "", assetId: "" }

     const {
            result: { assignedDirectories }
     } = await getIndexDocument(this.serviceConfig).catch(err => {
        throw new Error(`Bad repsonse`);
     });

     return {
        repoId: result.repoId;
        assetId: result.assetId
     }

}

public async function copyAsset(asset) {
   const res = await this.exploreIndexDocument();
   const repoId = res.repoId;
   return asset.copy(repoId);
}

I'm trying to test the function copyAsset, but it calls exploreIndexDocument which calls getIndexDocument. getIndexDocument is imported at the top of the file and lives in the module @ma/http. getIndexDocument makes an HTTP request.

How can I test copyAsset given that it calls getIndexDocument which makes an HTTP request?

Chris Hansen
  • 7,813
  • 15
  • 81
  • 165
  • 1
    _"I tried that but it returns an error."_ What error? Also, what is `MetaPromise`? – Alex Wayne Jan 20 '22 at 23:51
  • MetaPromise is a Promise. I get the following error -- Property 'resolves' does not exist on type 'SinonStubbedInstance(svc: MetaHTTPService | ServiceConfig) => _IMetaPromise "http://ns.meta.com/metacloud/rel/repository">, MetaResponseType>, any, unknown>>'. – Chris Hansen Jan 20 '22 at 23:55

2 Answers2

1

According to the docs, you can't stub an existing function.

You can:

// Create an anonymous sstub function
var stub = sinon.stub();

// Replaces object.method with a stub function. An exception is thrown
// if the property is not already a function.
var stub = sinon.stub(object, "method");

// Stubs all the object’s methods.
var stub = sinon.stub(obj);

What you can't do is stub just a function like:

var stub = sinon.stub(myFunctionHere);

This makes sense because if all you have is a reference to a function, then you can just create a new function to use instead, and then pass that into where ever your test needs it to go.


I think you just want:

const myStub = sandbox.stub().resolves({} as RepoResponseResult)

In your update it sounds like you want to put the stub on the AssetsController class. See this answer for more info on that, but in this case I think you want:

const myStub = sandbox
  .stub(AssetsController.prototype, 'exploreIndexDocument')
  .resolves({} as RepoResponseResult)

Now anytime an instance of AssetsController calls its exploreIndexDocument method, the stub should be used instead.

Playground

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • hi Alex, the issue is that the function that I'm testing called getIndexDocument which makes an HTTP request. I want to stub that function, but you say that I can't. What should I do instead? – Chris Hansen Jan 21 '22 at 00:16
  • Mocking strategies are heavily dependent on how you acquire a reference to the mocked thing, and how the mocked thing is used. Neither of those details are present in question, so I can't be of much help. – Alex Wayne Jan 21 '22 at 00:21
  • so I have a function copyAsset in the class AssetsController. It calls a function called exploreIndexDocument in the class AssetsController. That function calls getIndexDocument from another module. getIndexDocument makes an API call - essentially a get request to get a list of directories in a specific folder. I want to write a test for the function copyAsset; however, in order to do so, I have to stub the getIndexDocument function. But you are telling me that I cannot do that. – Chris Hansen Jan 21 '22 at 00:38
  • Add this info with code snippets showing how this function is imported and used to your original question, and I'll try help more. – Alex Wayne Jan 21 '22 at 00:40
  • ok I updated my question. – Chris Hansen Jan 21 '22 at 00:55
  • Answer updated! In general, mock the thing the function is on, not just the function. In this case, that's `AssetsController`. – Alex Wayne Jan 21 '22 at 01:11
  • thank you so much – Chris Hansen Jan 21 '22 at 02:06
1

I think most of your problems can be solved by revisiting your architecture. For example, instead of creating an explicit dependency on getIndexDocument within your AssetController class you can simply inject it in. This will allow you to swap implementations depending on the context.

type IndexDocumentProvider = (svc: MetaHTTPService | ServiceConfig) => MetaPromise<RepoResponseResult<IndexDocument>>;

interface AssetControllerOptions {
  indexDocumentProvider: IndexDocumentProvider
}

class AssetController {
  private _getIndexDocument: IndexDocumentProvider;

  public constructor(options: AssetControllerOptions) {
    this._getIndexDocument = options.indexDocumentProvider;
  }
}

Then you can use this._getIndexDocument wherever and not worry about how to make the original implementation behave like you want in your tests. You can simply provide an implementation that does whatever you'd like.

describe('copyAsset', () => {
  it('fails on index document error.', () => {
    const controller = new AssetController({
      indexDocumentProvider: () => Promise.reject(new Error(':('));
    });
    ....
  });

  it('copies asset using repo id.', () => {
    const controller = new AssetController({
      indexDocumentProvider: () => Promise.resolve({ repoId: "420" })
    });
    ...
  });
});

You can obviously use stubs instead of just functions or whatever if you need something fancy.

Above we removed an explicit dependency to an implementation and instead replaced it with a contract that must be provided to the controller. The is typically called Inversion of Control and Dependency Injection

Brenden
  • 1,947
  • 1
  • 12
  • 10