Sinon: 间谍 `returnValue` 不适用于生成器

创建于 2015-01-03  ·  20评论  ·  资料来源: sinonjs/sinon

当使用生成器函数时,间谍的返回值总是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运行。

2.x Unverified

所有20条评论

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 对诸如yieldasyncfunction*()等内容的了解。广泛的受众。

+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

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

NathanHazout picture NathanHazout  ·  3评论

byohay picture byohay  ·  3评论

akdor1154 picture akdor1154  ·  4评论

fearphage picture fearphage  ·  4评论

ndhoule picture ndhoule  ·  4评论