如何重构调用函数的函数,使其可以使用 typescript 和 sinon 进行测试?

How to refactor a function that calls a function so that it can be tested using typescript and sinon?

我有以下代码

import {
    getIndexDocument
} from "@assets";

class MetaController {
     
    public async exploreIndexDocument(): Promise<Asset | undefined> {
         const {
            result: { assignedDirectories }
         } = await getIndexDocument(this._serviceConfig).catch(err => {
             throw new Error(`[AssetsController] Bad response on discovering index doc because ${err}`);
        });
     }
}

如您所见,exploreIndexDocument 正在调用函数 getIndexDocument。我想为 exploreIndexDocument 编写一个测试,但我不能使用 sinon 存根 getIndexDocument,因为 sinon 不允许您存根函数。我该如何构建这个 class 才能做到这一点?

您需要某种方式来注入存根,以便您的 class 实例调用它而不是外部库。 This answer elaborates some alternatives. The alternative to injecting stubs is to replace the entire module you are importing. This is called using a link seam. 并列出了各种可帮助您这样做的模块加载器。

就我个人而言,我已经慢慢摆脱了模块模拟技术,并试图让自己处于依赖注入的领域(备选方案 1),因为无论底层环境如何,它都可以工作,而且任何新手程序员都可以阅读测试。侵入性最小的方法可以简单地是这样的:

import {
    getIndexDocument
} from "@assets";

class MetaController {
    private getIndexDocument: (config:object) => Promise<{assignedDirectories:any> };

    constructor(deps = {getIndexDocument}) {
        this.getIndexDocument = getIndexDocument;
    }
     
    public async exploreIndexDocument(): Promise<Asset | undefined> {
        const {
            result: { assignedDirectories }
        } = await this.getIndexDocument(this._serviceConfig).catch(err => {
            throw new Error(`[AssetsController] Bad response on discovering index doc because ${err}`);
        });
     }
}

您现在可以非常轻松地进行测试:

const fake = sinon.fake.resolves({ assignedDirectories: ['/foo/dir'] });
const controller = new MetaController({getIndexDocument: fake});
const promise = controller.exploreIndexDocument();

expect(fake.calledOnce).toBe(true);
// further assertions follow ... see https://sinonjs.org/releases/latest/fakes/