【问题标题】:sinon stub with es6-promisified object带有 es6 承诺对象的 sinon 存根
【发布时间】:2016-09-26 00:47:47
【问题描述】:

好的,我的设置如下: 使用 node 6.2、es6-promisify、sinon、sinon-as-promised 和 babel 转译对 es6 导入/导出的支持。

我的测试代码如下所示:

const client = restify.createJsonClient({
    url: 'http://www.example.com'
});
export let get = promisify(client.get, {thisArg: client, multiArgs: true});

export default function* () {
    yield get('/some/path');
}

然后在我的测试文件中我有这样的东西:

import * as m from mymodule;
it('should fail', function(done) {
    let stub = sinon.stub(m, 'get').rejects('i failed');
    client.get('/endpoint/that/leads/to/mymodule/call', function(err, req, res, data) {
        stub.called.should.be.eql(true); // assertion fails!!
        done();
    }
});

我也尝试过对原始的 client.get 调用存根,但这也不起作用。我唯一要做的就是在每次调用时即时做出承诺,并将原始的 client.get 存根,这看起来很蹩脚。例如:

export const client = restify.createJsonClient({
    url: 'http://www.example.com'
});
function get() {
    return promisify(client.get, {thisArg: client, multiArgs: true});
}

export default function* () {
    yield get('/some/path');
}

然后测试代码:

import {module_client} from mymodule;
it('should fail', function(done) {
    let stub = sinon.stub(module_client, 'get').yields('i failed');
    client.get('/endpoint/that/leads/to/mymodule/call', function(err, req, res, data) {
        stub.called.should.be.eql(true); // assertion succeeds
        done();
    }
});

所以问题是,如果不是很明显,为什么我的原始代码不起作用?有没有办法让存根工作而不用每次都承诺原始的restify(例如,其他人如何让这种事情工作)?

编辑:

当前代码如下所示:

const client = restify.createJsonClient({
    url: 'http://www.example.com'
});

export let get = promisify(client.get, {thisArg: client, multiArgs: true});

export default function*() {
    try {
        console.log(exports.get); // <= a large sinon stub object, I'll post that below
        yield exports.get(); // <= throws here, "exports.get is not a function"
    }
    catch(ex) {
        log.error('got an error', ex);
        throw ex;
    }
}

console.log 打印以下内容:

{ [Function: proxy]
  isSinonProxy: true,
  reset: [Function],
  invoke: [Function: invoke],
  named: [Function: named],
  getCall: [Function: getCall],
  getCalls: [Function],
  calledBefore: [Function: calledBefore],
  calledAfter: [Function: calledAfter],
  withArgs: [Function],
  matches: [Function],
  printf: [Function],
  calledOn: [Function],
  alwaysCalledOn: [Function],
  calledWith: [Function],
  calledWithMatch: [Function],
  alwaysCalledWith: [Function],
  ....

EDIT2:

FWIW,babel 生成的代码正在生成:

let get = exports.get = (0, _es6Promisify2.default)(client.get, { thisArg: client, multiArgs: true });

EDIT3:

好吧,超级奇怪。我改变了我的来源来做到这一点:

const client = restify.createJsonClient({
    url: 'http://www.example.com'
});

export let get = promisify(client.get, {thisArg: client, multiArgs: true});

export default function*() {
    try {
        let thePromise = exports.get(); // e.g. call exports.get on separate line from the yield
        yield thePromise; // and the throw now says 'undefined is not a function'. I should note that in both cases, the stack trace shows the error on node_modules/co/index.js at line 65.
    }
    catch(ex) {
        log.error('got an error', ex);
        throw ex;
    }
}

【问题讨论】:

  • get() 未导出,因此 module_client.get() 在您的测试代码中未定义。但即使它被导出,仍然存在由 ES6 引起的问题(至少,我认为)。我会看看我能不能写出来作为答案。
  • 如果你的意思是'get'没有在第二个例子中被导出,你是对的,我确实有一个错误。我的意思是,原来的 restify.client 被导出为 module_client。我正在扼杀它。哪个有效。
  • 哦,对不起,当然是!我的回答应该仍然适用于第一种情况,第二种情况通过在运行时而不是在导入时进行承诺来解决它。

标签: javascript testing ecmascript-6 sinon es6-promise


【解决方案1】:

问题最终与 ES6 导入/导出的工作方式有关,特别是它们如何使您的代码看起来更好但阻止了轻松的间谍/存根。

以这个模块为例:

// my-module.js
function someFunction() {
  console.log('original');
};

export let get = someFunction;

export default function() {
  get();
};

该代码的测试用例可能如下所示:

import * as sinon from 'sinon';
import * as should from 'should';
import setup, * as myModule from './my-module';

it('should call get()', () => {
  let stub = sinon.stub(myModule, 'get');
  setup();
  stub.called.should.eql(true);
});

您会看到原始的get() 被调用,而不是存根。这是因为在模块中,get 是本地(对模块)引用。 Sinon 正在导出对象中对同一函数的 另一个 引用。

要做到这一点,您需要在导出的对象中使用本地引用,而不是在模块中使用本地引用:

export default function() {
  exports.get();
};

唉,这导致代码更丑。

【讨论】:

  • 所以我按照你说的做了,这确实有道理。但我得到另一个错误。在被测代码中,它会抛出我调用exports.get 说“exports.get 不是函数”的地方。如果我在调用之前对exports.get 执行console.log,它表明它是一个对象——sinon 存根对象。我可以将其粘贴到我的问题中,但对象非常大。
  • 我编辑了我的答案,所以代码更模仿了你的第一个例子。
  • 好的,exports.get 是正确的对象,它是 sinon 存根代理对象。所以我的困惑是为什么现在说exports.get不是一个函数会崩溃。我的意思是,它不是一个函数,它是一个代理。但这与 sinon 在我的代码中其他地方所做的没有什么不同。所以……?
  • @Kevin 查看this gist。这也失败了,出现了类似的错误。看起来产生一个 Sinon 存根会导致错误。仍然不知道为什么,但至少它是可重现的。
  • 好吧,这至少让人放心,它可以被复制。所以问题是,我不认为它会产生一个 sinon 存根。它正在调用一个返回承诺的函数(exports.get)。并且 co 知道如何处理已产生的承诺(或者,我认为,一般来说,thenables)。因此,对我来说,它说“exports.get 不是一个函数”的事实意味着,没有任何结果。因为它需要在函数被 yield 挂起之前评估函数并产生结果(promise)。
猜你喜欢
  • 2015-01-24
  • 2021-02-23
  • 2018-03-29
  • 2015-07-31
  • 2017-08-19
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
相关资源
最近更新 更多