typescript - 在 TypeScript 中进行单元测试时,存根依赖项的正确方法是什么?
问题描述
我正在将一组 Firebase 云函数从 Javascript 迁移到 Typescript。使用 JS,我能够使用 mocha、chai 和 sinon 进行单元测试,并存根各种数据库依赖项进行测试。使用 TS,我遇到了我不理解的问题和意外行为。
该模式类似于:
mainFunction
调用helperFunction
哪个调用nestedHelperFunction
。
当我用一些测试数据nestedHelperFunction
调用时,我想在我的测试中存根或监视。mainFunction
mainFunction
在一个index.ts
和中helperFunction
,并且nestedHelperFunction
在一个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
创建helperFunction
和nestedHelperFunction
方法中,而不是“顶级”函数。
// 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 中遇到了问题。
谢谢你的帮助。
解决方案
您是正确的,它与导入有关:如果您查看生成的代码,您会看到在 内util.ts
,对本地函数的调用nestedHelperFunction
是对本地函数的直接引用(即nestedHelperFunction()
,不是类似的东西util.nestedHelperFunction()
)。
您的存根当前仅替换模块对象 ( util.nestedHelperFunction
) 上的引用,而不是本地的。
您确实需要使用Rewire之类的东西来将其剔除。
你提到你有 Rewire 和 TS 的问题。我最近也对此进行了一些实验,但它的打字似乎无法正常工作(不再?)。主要问题是它的类型不会导出与导出的“主”符号同名的命名空间。
作为临时解决方法(直到“官方”类型被修复),您可以将以下内容保存到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.ts
and index.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)
推荐阅读
- php - 我上传了一个 php 文件,从 db 中提取的内容消失了
- java - 什么数据类型在java中放置超过64位的值,不包括BigInteger
- python - 如何阻止 Dataframe 子集“记住”旧值
- javascript - 我在这里缺少什么脚本?
- iis - 无法安装 Windows 功能 IIS
- java - 为什么maven不能解析父版本中的表达式?
- windows - git 扩展在启动时崩溃
- python - 使用 NetworkX 有效地检查路径在图中是否有效?
- javascript - 如何将 $q 提供程序插入到未包装到角度模块中的类中?
- tableau-api - 如何在 Tableau 的计算字段中使用 UNION 运算符。