- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我的代码如下:
// example.js
export function doSomething() {
if (!testForConditionA()) {
return;
}
performATask();
}
export function testForConditionA() {
// tests for something and returns true/false
// let's say this function hits a service or a database and can't be run in tests
...
}
export function performATask() {
...
}
// example.test.js
import * as example from 'example';
it('validates performATask() runs when testForConditionA() is true', () => {
const testForConditionAStub = sinon.stub(example, 'testForConditionA').returns(true);
const performATaskSpy = sinon.stub(example, 'performATask');
example.doSomething();
expect(performATaskSpy.called).to.equal(true);
});
(我知道,这是一个人为的例子,但我尽量保持简短)
我还没有找到使用 Sinon 模拟 testForConditionA() 的方法。
我知道有变通办法,比如
A) 将 example.js 中的所有内容放入一个类中,然后可以 stub 该类的函数。
B) 将 testForConditionA() (和其他依赖项)从 example.js 移到一个新文件中,然后使用 proxyquire
C) 将依赖项注入(inject) doSomething()
但是,这些选项都不可行 - 我在大型代码库中工作,许多文件需要重写和大修。我已经搜索过这个主题,并且看到了其他几篇文章,例如 Stubbing method in same file using Sinon ,但是除了将代码重构为一个单独的类(或有人建议的工厂),或者重构为一个单独的文件并使用 proxyquire 之外,我还没有找到解决方案。我过去曾使用过其他测试和模拟库,所以令人惊讶的是 Sinon 无法做到这一点。或者是吗?关于如何在不重构它试图测试的代码的情况下对函数进行 stub 的任何建议?
最佳答案
此位来自 a very related answer (我的),说明了为什么它并不那么令人惊讶:
ES modules are not mutable by default, which means Sinon can't do zilch.
EcmaScript 规范规定了这一点,因此当前改变导出的唯一方法是让运行时不遵守规范。这基本上就是 Jest 所做的:它提供自己的运行时,将导入调用转换为等效的 CJS 调用 (require
) 调用,并在该运行时提供自己的 require
实现以 Hook 进入加载过程。生成的“模块”通常具有您可以覆盖的可变导出(即 stub )。
Jest 也不支持原生(因为没有源代码的转译/修改)ESM。跟踪问题 4842和 9430这有多复杂(需要更改 Node)。
所以,不,诗乃自己是做不到的。它只是一个 stub 库。它不会触及运行时或做任何神奇的事情,因为它必须在任何环境下都能正常工作。
现在回到您最初的问题:测试您的模块。我看到这种情况发生的唯一方法是通过某种依赖注入(inject)机制(您在备选方案 C 中提到过)。您显然有一些(内部/外部)状态是您的模块所依赖的,因此这意味着您需要一种从外部更改该状态或注入(inject)测试替身(您正在尝试的)的方法。
一个简单的方法就是创建一个严格用于测试的 setter:
function callNetworkService(...args){
// do something slow or brittle
}
let _doTestForConditionA = callNetworkService;
export function __setDoTestForConditionA(fn){
_doTestForConditionA = fn;
}
export function __reset(){
_doTestForConditionA = callNetworkService;
}
export function testForConditionA(...args) {
return _doTestForConditionA(...args);
}
然后您可以像这样简单地测试您的模块:
afterEach(() => {
example.__reset();
});
test('that my module calls the outside and return X', async () => {
const fake = sinon.fake.resolves({result: 42});
example.__setDoTestForConditionA(fake);
const pendingPromise = example.doSomething();
expect(fake.called).to.equal(true);
expect((await pendingPromise).result).toEqual(42);
});
是的,您确实修改了您的 SUT 以允许测试,但我从未发现这一切令人反感。无论框架(Jasmine、Mocha、Jest)或运行时(浏览器、Node、JVM)如何,该技术都适用,并且读起来很好。
您确实提到将依赖项注入(inject)到实际上依赖于它们的函数中,这会产生一些会传播到整个代码库的问题。
我想通过展示我过去使用过的技术来挑战一下。请参阅我在 Sinon 问题跟踪器上的评论:https://github.com/sinonjs/sinon/issues/831#issuecomment-198081263
我使用这个示例来展示如何在构造函数中注入(inject) stub ,而该构造函数的普通使用者无需关心这些 stub 。当然,确实需要您使用某种Object
来不添加其他参数。
/**
* Request proxy to intercept and cache outgoing http requests
*
* @param {Number} opts.maxAgeInSeconds how long a cached response should be valid before being refreshed
* @param {Number} opts.maxStaleInSeconds how long we are willing to use a stale cache in case of failing service requests
* @param {boolean} opts.useInMemCache default is false
* @param {Object} opts.stubs for dependency injection in unit tests
* @constructor
*/
function RequestCacher (opts) {
opts = opts || {};
this.maxAge = opts.maxAgeInSeconds || 60 * 60;
this.maxStale = opts.maxStaleInSeconds || 0;
this.useInMemCache = !!opts.useInMemCache;
this.useBasicToken = !!opts.useBasicToken;
this.useBearerToken = !!opts.useBearerToken;
if (!opts.stubs) {
opts.stubs = {};
}
this._redisCache = opts.stubs.redisCache || require('./redis-cache');
this._externalRequest = opts.stubs.externalRequest || require('../request-helpers/external-request-handler');
this._memCache = opts.stubs.memCache || SimpleMemCache.getSharedInstance();
}
(请参阅问题跟踪器以获取更多评论)
没有什么可以强制任何人提供 stub ,但是测试可以提供它们来覆盖依赖项的工作方式。
关于javascript - Sinon stub 定义在同一文件中的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70873822/
C语言sscanf()函数:从字符串中读取指定格式的数据 头文件: ?
最近,我有一个关于工作预评估的问题,即使查询了每个功能的工作原理,我也不知道如何解决。这是一个伪代码。 下面是一个名为foo()的函数,该函数将被传递一个值并返回一个值。如果将以下值传递给foo函数,
CStr 函数 返回表达式,该表达式已被转换为 String 子类型的 Variant。 CStr(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CSng 函数 返回表达式,该表达式已被转换为 Single 子类型的 Variant。 CSng(expression) expression 参数是任意有效的表达式。 说明 通常,可
CreateObject 函数 创建并返回对 Automation 对象的引用。 CreateObject(servername.typename [, location]) 参数 serv
Cos 函数 返回某个角的余弦值。 Cos(number) number 参数可以是任何将某个角表示为弧度的有效数值表达式。 说明 Cos 函数取某个角并返回直角三角形两边的比值。此比值是
CLng 函数 返回表达式,此表达式已被转换为 Long 子类型的 Variant。 CLng(expression) expression 参数是任意有效的表达式。 说明 通常,您可以使
CInt 函数 返回表达式,此表达式已被转换为 Integer 子类型的 Variant。 CInt(expression) expression 参数是任意有效的表达式。 说明 通常,可
Chr 函数 返回与指定的 ANSI 字符代码相对应的字符。 Chr(charcode) charcode 参数是可以标识字符的数字。 说明 从 0 到 31 的数字表示标准的不可打印的
CDbl 函数 返回表达式,此表达式已被转换为 Double 子类型的 Variant。 CDbl(expression) expression 参数是任意有效的表达式。 说明 通常,您可
CDate 函数 返回表达式,此表达式已被转换为 Date 子类型的 Variant。 CDate(date) date 参数是任意有效的日期表达式。 说明 IsDate 函数用于判断 d
CCur 函数 返回表达式,此表达式已被转换为 Currency 子类型的 Variant。 CCur(expression) expression 参数是任意有效的表达式。 说明 通常,
CByte 函数 返回表达式,此表达式已被转换为 Byte 子类型的 Variant。 CByte(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CBool 函数 返回表达式,此表达式已转换为 Boolean 子类型的 Variant。 CBool(expression) expression 是任意有效的表达式。 说明 如果 ex
Atn 函数 返回数值的反正切值。 Atn(number) number 参数可以是任意有效的数值表达式。 说明 Atn 函数计算直角三角形两个边的比值 (number) 并返回对应角的弧
Asc 函数 返回与字符串的第一个字母对应的 ANSI 字符代码。 Asc(string) string 参数是任意有效的字符串表达式。如果 string 参数未包含字符,则将发生运行时错误。
Array 函数 返回包含数组的 Variant。 Array(arglist) arglist 参数是赋给包含在 Variant 中的数组元素的值的列表(用逗号分隔)。如果没有指定此参数,则
Abs 函数 返回数字的绝对值。 Abs(number) number 参数可以是任意有效的数值表达式。如果 number 包含 Null,则返回 Null;如果是未初始化变量,则返回 0。
FormatPercent 函数 返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 FormatPercent(expression[,NumDigitsAfterD
FormatNumber 函数 返回表达式,此表达式已被格式化为数值。 FormatNumber( expression [,NumDigitsAfterDecimal [,Inc
我是一名优秀的程序员,十分优秀!