当使用生成器函数时,间谍的返回值总是undefined
。 这是一个失败的测试用例:
require('should');
var sinon = require('sinon');
var co = require('co');
var foo = {
bar: function() {
return 'bar';
},
biz: function *() {
return 'biz';
}
}
describe('Return value', function() {
it('should work with regular functions', function() {
sinon.spy(foo, 'bar');
foo.bar();
foo.bar.firstCall.returnValue.should.equal('bar');
});
it('should work with generator functions', co.wrap(function*() {
sinon.spy(foo, 'biz');
var result = yield foo.biz();
result.should.equal('biz');
foo.biz.firstCall.returnValue.should.equal('biz');
}));
});
使用npm install co mocha should sinon && ./node_modules/.bin/mocha --harmony index.js
运行。
Sinon 还不支持 ES6 特性。 这方面的工作还没有开始,所以我不知道什么时候可以工作。
@mantoni ,对,这更像是一个功能请求 :) 你想让它保持打开状态,例如,使用某种标签(“es6”?),这样当它被添加到路线图时,更容易重新- 评估需要做什么?
这将是一个很棒的功能。
@ruimarinho我发现了这个https://github.com/ingameio/SinonES6.JS
用 require("sinon-es6") 替换 require("sinon") 似乎使您的测试通过
--
@fatso83于 2017 年 6 月 22 日编辑:
这似乎不是真的。 我手动测试了这个。 它同样失败,这完全有道理,因为sinon-es6
_only实现了对生成器的支持以模拟 _。
有人在做这个吗?
@emgeee不是我知道的。
+1 @ruimarinho sinon.js 具有 es6 功能会很棒!
@ingameio 是否愿意对您的更改进行公关?
@fatso83感谢您的建议!
我会尽快做 PR ;)
针对打包版本运行 buster-test 时遇到问题。 我做了一些调试,但是这几天我的时间很短,所以我无法解决这个问题。
如果有人想帮助我,我可以将更改推送到我的 fork,只要告诉我,我就会去做。
@gaguirre这是个好消息。 Maximillian 刚刚将所有测试转换为 Mocha,并在测试设置中进行了一些更改,所以如果您从master
中提取最新更改,也许事情会解决?
@gaguirre万一有人偶然发现了这个线程,我认为将更改推送到您的 fork 可能是一个想法,以便路人可以查看它。
@fatso83我将第一个版本推送到underscope/sinon 。
使用不支持 es6 的引擎(在本例中为 phantomjs)运行测试时会出现主要问题:解析代码时失败,因此我使用eval()
作为解决方法,因为可以被 try/ 包围抓住。 我认为这是不可接受的,但这是一个起点。
现在我正在尝试将生成器调用包装在另一个文件中并动态要求它,但它不起作用,我猜是因为 browserify 它甚至捆绑了动态要求的文件。 我想知道在运行npm run test-headless
时是否可以排除某些文件。
您认为最好的解决方案是什么?
感谢您的反馈, @gaguirre 。 所以基本问题是,如果引擎不支持解析,我们需要某种方式来避免解析。 这比简单的特征检测要难一些,就像我们使用 WebWorkers 等做的那样。
只是委派实际检查就像if(require('generator-support')){ ... }
一样简单,但这并不能解决实际的解析问题。
@mantoni和 @mroderick 关于如何干净地处理这个问题的任何想法?
PS 我几个小时后会去线下度假,所以要等到复活节后的某个时间才能阅读答案。
我不知道这种级别的生成器存根是否真的有效(特别是在面向协程的场景中)——似乎一些异步行为的模拟可能是有序的。 但我确实认为有办法解决解析问题。
建议:
将生成器代码提取到一个单独的文件中,并且require()
仅在包装生成器函数时根据需要提取该文件,即:
?/es6-support.js:
"use strict";
exports.getGeneratorWrapper = function(method, ctx, args) {
return function* () {
return mockObject.invokeMethod(method, ctx, args);
}
}
lib/sinon/mock.js->期望...:
var wrapper = function () {
return mockObject.invokeMethod(method, this, arguments);
});
if (/^function\s*\*/.test(method.toString())) {
wrapper = require('es6-support').getGeneratorWrapper(method, this, arguments);
}
wrapMethod(this.object, method, wrapper);
(也对单元测试进行类似的操作,并确保跟踪代码覆盖率不会导致自动包含“es6-support”文件。)
替代建议:
在 sinon 2.0 准备发布时,ES5 兼容性是否仍然是一个有价值的目标? 或许是时候告诉少数仍然支持没有转译器用户的遗留 ES5 环境的人,他们将在 1.x 中被抛在后面。
@evan-king 条件要求不是解决方案,因为它会破坏我们的浏览器构建。 由于条件需求会破坏依赖图的静态分析,WebPack、Rollup 和 Browserify 等工具将无法处理它。 它要么完全进入,要么完全退出。
是的,ES5 兼容性是一回事。 ES6 生成器支持充其量是粗制滥造的,强迫人们使用额外的工具来在他们的项目中使用 Sinon 将提高进行测试的标准。 对许多人来说,这个门槛已经足够高了。 能够简单地包含一个脚本标签以使其工作是恕我直言的重要功能。 我们可能会在某个时候破坏 ES5 的兼容性,但它不在我们的路线图上,甚至还没有针对 Sinon 3 进行讨论。Sinon 2 实际上已经推出了很长一段时间。 只有一些小麻烦阻止我们进行正式发布。
基本上有两种方法可以在不破坏现有 ES5 运行时兼容性的情况下获得 ES6 兼容性,我可以提出来,我不太确定最后一种(还没有测试过):
这是一个 hack,但相当简单。 这意味着我们可以依靠静态资产内联和 ES6 特性测试来决定是否评估所需的模块。 这将确保 ES5 兼容性(只有在运行时支持语法时才会修补 Sinon),无论是库制造商还是客户端用户都不需要 Babel 形式的额外工具,并且测试 ES6 将破坏 ES5 浏览器无论如何都会失败。
假设brfs之类的东西已经到位,代码将如下所示:
_runtime-features.js_
var features = {};
try {
new Function("var foo = function* foo(){ }") ;
features.generatorSupport = true;
}
catch(err){ features.generatorSupport = false; }
module.exports = features;
_es6-generator-wrapper.js_
return function* () {
return mockObject.invokeMethod(method, ctx, args);
}
_es6.js_
var features = require('./runtime-features');
if( features.generatorSupport ) {
var code = fs.readFileSync('./es6-generator-wrapper.js');
module.exports.getGeneratorWrapper = new Function("mockObject", "method", "ctx", "args", code);
} else {
module.exports.getGeneratorWrapper = function() { throw new TypeError("You tried using a generator function in a runtime only capable of running ES5 code"); }
}
这是我不确定的,因为我还没有真正尝试过。 如果我们通过 Babel 将构建转换为 ES5,我们可以在所有地方编写 ES6 代码,避免像我上面那样跳过箍,我们仍然可以对生成器使用相同类型的检查。 它们将仅使用 ES5 结构来实现。 当然,在 ES5 浏览器中测试 ES6 还是会失败。 这与客户端上的前一个具有相同的优势,但我们可能会阻碍 ES2015 对诸如yield
、 async
、 function*()
等内容的了解。广泛的受众。
+1
@rpavlovs ,在线程中添加 +1 确实没有意义。 如果您需要表达自己的情绪,GitHub UI 在每条评论顶部都有一个“添加反应”按钮。 +1 将无济于事。 另一方面,实现上述建议之一(或更聪明的建议)的拉取请求有更好的机会解决这个问题😄
我想输入生成器的 API 支持的外观,因为在使用了几个小时之后,我仍然不确定人们希望看到什么。
为了开始充实这一点,我创建了一个新分支,其中包含@ingameio对 mock api 的修改,同时不破坏 ES5 兼容性(使用上面提到的 hack)。
让我有点恼火的是,我真的不知道如何测试 Ingameio 的原始更改,因为没有一个测试示例有效 - 甚至 fork 中的示例都不完整,而且我无法获得破坏前/后的东西变化。
发电机是简单的东西:温和的同步生物,记住他们的过去。 因此,请不要用co
和其他不相关的东西来混淆任何示例,因为这样会更难看出想要/不起作用的东西。 例如,上面的例子相当复杂,它似乎也搞错了yield
所做的事情,因为它期望“yield 表达式”的返回值与“yield value”相同。 收益的result
是传递给生成器的next()
( MDN ) 的值
我确实意识到使用co
的示例可能仅使用它来直接在 Mocha 测试中使用yield
,但只需将您的示例包装在 IIFE 或其他实现相同目的的方式中为了更清楚。
这是对生成器如何支持的简单测试(在今天的 Sinon 中工作):
require("should");
var sinon = require("../sinon");
var foo = {
bar: function () {
return "bar";
},
biz: function *() {
return "biz";
}
};
describe("generator support", function () {
it('should work with generator functions', function(){
var spy = sinon.spy(foo, 'biz');
var iterator = foo.biz();
var result = iterator.next();
result.value.should.equal('biz');
result.done.should.equal(true);
spy.firstCall.returnValue.should.be.an.Object();
spy.firstCall.returnValue.next.should.be.a.Function();
});
});
现在,我们想要哪些 API 扩展?
从最初的测试中,我假设我们希望看到类似的东西
foo.biz.firstGeneratedValue.should.equal('biz');
或者foo.biz.generatedValue[0].should.equal('biz');
?
抄送@ruimarinho
我正在关闭这个问题,因为原始测试有一个错误,我看不到在 Sinon 中的生成器处理有任何问题。
请加入关于如何处理生成器(及其关联的迭代器)的 API 的讨论,请参见 issue #1467