【问题标题】:What is the proper way to stub dependencies when unit testing in TypeScript?在 TypeScript 中进行单元测试时,存根依赖项的正确方法是什么?
【发布时间】:2019-12-13 03:47:44
【问题描述】:

我正在将一组 Firebase 云函数从 Javascript 迁移到 Typescript。使用 JS,我能够使用 mocha、chai 和 sinon 进行单元测试,并存根各种数据库依赖项进行测试。对于 TS,我遇到了一些我不理解的问题和意外行为。

模式类似于: mainFunction 调用helperFunction,后者调用nestedHelperFunction

当我用一些测试数据调用mainFunction 时,我想在我的测试中存根或监视nestedHelperFunction

mainFunction 位于 index.ts 中,helperFunctionnestedHelperFunction 位于 utils.ts 文件中。

奇怪行为的例子

// index.ts
import * as utils from './utils';


export async function mainFunction() {
    console.log('Starting mainFunction...');

    const promiseResults = await Promise.all([
        Promise.resolve('One'),
        utils.helperFunction(),
        Promise.resolve('Three'),
    ]);

    console.log(promiseResults);
    return 1;
}
// utils.ts
export async function helperFunction() {
    const newString = await nestedHelperFunction();
    return 'helperFunction Result | ' + newString;
}

export async function nestedHelperFunction() {
    return '***Nested***';
}

测试文件

//index.test.ts
import * as myFunctions from '../index';
import * as utils from '../utils';
import * as sinon from 'sinon';
import * as chai from 'chai';

const expect = chai.expect;


describe("Test Suite", () => {

    let functionSpy: sinon.SinonStub;

    beforeEach(() => {
        functionSpy = sinon.stub(utils, 'helperFunction');
        functionSpy.returns(Promise.resolve('Stubbed Function Results!'))
    });

    afterEach(() => {
        functionSpy.restore();
    });

    it('should resolve and call the correct functions.', async () => {
        const returnValue = await myFunctions.mainFunction();

        expect(returnValue).to.equal(1);
        expect(functionSpy.callCount).to.equal(1);
    })
})

输出: 测试通过,我得到:[ 'One', 'Stubbed Function Results!', 'Three' ]

但是,如果我尝试对nestedHelperFunction 存根,它就不起作用。

// index.test.js
import * as myFunctions from '../index';
import * as utils from '../utils';
import * as sinon from 'sinon';
import * as chai from 'chai';

const expect = chai.expect;


describe("Test Suite", () => {

    let functionSpy: sinon.SinonStub;

    beforeEach(() => {
        functionSpy = sinon.stub(utils, 'nestedHelperFunction'); // Changed
        functionSpy.returns(Promise.resolve('Stubbed Function Results!'))
    });

    afterEach(() => {
        functionSpy.restore();
    });

    it('should resolve and call the correct functions.', async () => {
        const returnValue = await myFunctions.mainFunction();

        expect(returnValue).to.equal(1);
        expect(functionSpy.callCount).to.equal(1);
    })
})

输出 测试失败,我得到未修改的输出:[ 'One', 'helperFunction Result | ***Nested***', 'Three' ]

为什么在存根 nestedHelperFunction 时它不起作用,但适用于 helperFunction

工作示例

utils.ts 内创建helperFunctionnestedHelperFunction 作为类上的方法而不是“顶级”函数是可行的,但我不明白为什么。

// utils.ts

export class Utils {
    static async helperFunction(): Promise<string> {
        const newString = await this.nestedHelperFunction();
        return 'helperFunction Result | ' + newString;
    }

    static async nestedHelperFunction (): Promise<string> {
        return '***Nested Output***';
    }
}

测试文件

// index.test.ts
import {mainFunction} from '../index';
import {Utils} from '../utils';
import sinon from 'sinon';
import * as chai from 'chai';

const expect = chai.expect;


describe("Test Suite", () => {

    let functionSpy: sinon.SinonStub;

    beforeEach(() => {
        functionSpy = sinon.stub(Utils, 'nestedHelperFunction');
        functionSpy.returns(Promise.resolve('Stubbed Function Results!'));
    });

    afterEach(() => {
        functionSpy.restore();
    });

    it('should resolve and call the correct functions.', async () => {
        const returnValue = await mainFunction();

        expect(returnValue).to.equal(1);
        expect(functionSpy.callCount).to.equal(1);
    })
})
// index.ts
import {Utils} from './utils';

export async function mainFunction() {
    console.log('Starting mainFunction...');

    const promiseResults = await Promise.all([
        Promise.resolve('One'),
        Utils.helperFunction(),
        Promise.resolve('Three'),
    ]);

    console.log(promiseResults);
    return 1;
}

输出 测试通过,我得到所需/预期的输出:[ 'One', 'helperFunction Result | Stubbed Function Results!', 'Three' ]

我读过的材料向我建议了导入 es6 模块或 Typescript 如何编译并可以更改导入项目的名称。在 Javascript 中,我使用 Rewire 来设置存根,其中一些是私有函数,但我在 Typescript 中遇到了问题。

感谢您的帮助。

【问题讨论】:

    标签: typescript unit-testing sinon


    【解决方案1】:

    你是对的,它与导入有关:如果你查看generated code,你会看到在util.ts 中,对nestedHelperFunction 的调用是对本地函数的直接引用(即@987654326 @,而不是 util.nestedHelperFunction())。

    您的存根当前仅替换模块对象 (util.nestedHelperFunction) 上的引用,而不是本地的。

    您确实需要使用Rewire 之类的东西来存根。


    您提到您在使用 Rewire 和 TS 时遇到了问题。我最近也对此进行了一些实验,但它的打字似乎无法正常工作(不再?)。 主要问题是它的类型不会导出与导出的“主”符号同名的命名空间。

    作为临时解决方法(在修复'official' typings 之前),您可以将以下内容保存到rewire.d.ts

    declare module "rewire" {
        // Type definitions for rewire 2.5
        // Project: https://github.com/jhnns/rewire
        // Definitions by: Borislav Zhivkov <https://github.com/borislavjivkov>
        //                 Federico Caselli <https://github.com/CaselIT>
        // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
        // TypeScript Version: 2.3
    
        namespace rewire {
            interface RewiredModule {
                /**
                 * Takes all enumerable keys of obj as variable names and sets the values respectively. Returns a function which can be called to revert the change.
                 */
                __set__(obj: { [variable: string]: any }): () => void;
                /**
                 * Sets the internal variable name to the given value. Returns a function which can be called to revert the change.
                 */
                __set__(name: string, value: any): () => void;
                /**
                 * Returns the private variable with the given name.
                 */
                __get__<T = any>(name: string): T;
                /**
                 * Returns a function which - when being called - sets obj, executes the given callback and reverts obj. If callback returns a promise, obj is only reverted after
                 * the promise has been resolved or rejected. For your convenience the returned function passes the received promise through.
                 */
                __with__(obj: { [variable: string]: any }): (callback: () => any) => any;
            }
        }
    
        /**
         * Returns a rewired version of the module found at filename. Use rewire() exactly like require().
         */
        function rewire<T = { [key: string]: any }>(filename: string): rewire.RewiredModule & T;
        export = rewire;
    }
    

    然后(使用未修改的utils.tsindex.ts)使用以下index.tests.ts

    import rewire = require("rewire");
    
    import * as sinon from 'sinon';
    import { expect } from 'chai';
    
    const myFunctions = rewire<typeof import("../index")>("../index");
    const utils = rewire<typeof import("../utils")>("../utils");
    
    describe("Test Suite", () => {
    
        let functionSpy: sinon.SinonStub;
        let restoreRewires: Array<() => void>;
    
        beforeEach(() => {
            restoreRewires = [];
            functionSpy = sinon.stub();
            functionSpy.returns(Promise.resolve('Stubbed Function Results!'))
            restoreRewires.push(
                utils.__set__("nestedHelperFunction", functionSpy),
                myFunctions.__set__("utils", utils),
            );
        });
    
        afterEach(() => {
            restoreRewires.forEach(restore => restore());
        });
    
        it('should resolve and call the correct functions.', async () => {
            const returnValue = await myFunctions.mainFunction();
    
            expect(returnValue).to.equal(1);
            expect(functionSpy.callCount).to.equal(1);
        });
    });
    

    这会输出你想要的结果:

      Test Suite
    Starting mainFunction...
    [ 'One',
      'helperFunction Result | Stubbed Function Results!',
      'Three' ]
        ✓ should resolve and call the correct functions.
    
    
      1 passing (31ms)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-03
      • 1970-01-01
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 2014-09-01
      • 2012-11-17
      • 1970-01-01
      相关资源
      最近更新 更多