如何存根 returns 一个承诺的功能?

How to stub function that returns a promise?

我正在尝试使用 sinon 存根函数。该函数具有以下签名

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

这是正确的子方法吗

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

我试过了,但是 returns 出错了。

下面是这个函数的调用方式。

我有一个名为 AssetsController 的 class 具有以下功能

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

我正在尝试测试函数 copyAsset,但它调用了 exploreIndexDocument,后者又调用了 getIndexDocument。 getIndexDocument 在文件顶部导入并存在于模块@ma/http 中。 getIndexDocument 发出 HTTP 请求。

如果 copyAsset 调用发出 HTTP 请求的 getIndexDocument,我该如何测试它?

根据 to the docs,您不能存根现有函数。

您可以:

// 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);

你不能做的只是存根函数,如:

var stub = sinon.stub(myFunctionHere);

这是有道理的,因为如果您拥有的只是对函数的引用,那么您只需创建一个新函数来代替使用,然后将其传递到测试需要它的地方。


我想你只是想要:

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

在您的更新中,您似乎想将存根放在 AssetsController class 上。 See this answer 了解更多信息,但在这种情况下,我认为您需要:

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

现在只要 AssetsController 的实例调用它的 exploreIndexDocument 方法,就应该使用存根。

Playground

我认为您的大部分问题都可以通过重新访问您的架构来解决。例如,不是在 AssetController class 中创建对 getIndexDocument 的显式依赖,而是可以简单地将其注入。这将允许您根据上下文交换实现。

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

然后您可以在任何地方使用 this._getIndexDocument 而不必担心如何使原始实现在测试中表现得像您想要的那样。您可以简单地提供一个执行任何您想要的实现。

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" })
    });
    ...
  });
});

如果你需要一些花哨的东西,你显然可以使用 stubs 而不仅仅是函数或其他任何东西。

上面我们删除了对实现的显式依赖,取而代之的是必须提供给控制器的契约。通常称为 Inversion of ControlDependency Injection