使用 Sinon 在同一文件中存根方法

Stubbing method in same file using Sinon

我正在尝试对文件中的一个函数进行单元测试,同时对同一文件中的另一个函数进行存根,但是没有应用模拟,而是调用了真实的方法。这是一个例子:

// file: 'foo.js'

export function a() {
   // .....
}

export function b() { 
   let stuff = a(); // call a
   // ...do stuff
}

我的测试:

import * as actions from 'foo';

const aStub = sinon.stub(actions, 'a').returns('mocked return');
actions.b(); // b() is executed, which calls a() instead of the expected aStub()

一些重组可以使这项工作。

我使用了 commonJS 语法。在 ES6 中也应该以相同的方式工作。

foo.js

const factory = {
  a,
  b,
}
function a() {
  return 2;
}

function b() {
  return factory.a();
}

module.exports = factory;

test.js

const ser = require('./foo');
const sinon = require('sinon');

const aStub = sinon.stub(ser, 'a').returns('mocked return');
console.log(ser.b());
console.log(aStub.callCount);

输出

mocked return

1

虽然上面的方法确实有效,但它绝对是一种解决方法,因为我的 linter 很快就通知了。

我最终分离模块并使用 proxyquire。该库允许您轻松地将任何/所有导出替换为您选择的导出,包括 sinon 存根间谍或模拟。例如:

在b.js

export const fnB = () => 'hey there!';

在a.js

import { fbB } from 'b.js';
export const fnA = () => fbB();

在a.test.js

import { noCallThru } from 'proxyquire';
const proxyquireStrict = noCallThru();
const stubB = stub().returns('forced result');
const moduleA = proxyquireStrict('a.js', {
    'b.js' : { fnB: stubB }
}).fnA; 

console.log(fnA()); // 'forced result'

上面提到的方法(使用factory收集函数)效果很好;但是,eslint 不喜欢使用尚未声明的 variable/function。因此我建议稍微修改一下:

// my-functions.js
export const factory = {};
export const funcA = () => {
  return facory.funcB();
};
factory.funcA = funcA;
export const funcB = () => true;
factory.funcB = funcB;

// my-functions-test.js
import {factory, funcA, funcB} from './path/to/my-functions';

describe('MyFunctions | funcA', () => {
  test('returns result from funcB call', () => {
    const funcBStub = sinon.stub(factory, 'funcB').returns(false);

    // Test that the function does not throw errors
    let result;
    expect(() => (result = funcA())).not.toThrow();

    // Test that the return value is that of the mock rather than the original function
    expect(result).toEqual(false);

    // Test that the stub was called
    expect(funcBStub.called).toEqual(true);
  });
});

// Don't forget to test funcB independently ;)

重要的区别是将文件中的函数添加到 factory 中,因为它们被定义以避免违反 eslint 规则。这可能导致问题的唯一情况是,如果您尝试在同一文件中调用这些函数中的一个,然后再定义它们。示例:

// my-functions-1.js
export const factory = {};

export const funcA = () => {
  factory.funcB();
};
factory.funcA = funcA;

// Since the code execution runs from top to bottom, calling funcA here means that funcB has not yet been added to factory
funcA(); // Throws an error since factory.funcB() is not a function (yet)

export const funcB = () => true;
factory.funcB = funcB;

我更喜欢这种使用 "collector" 在同一个文件中调用函数的技术,因为为您编写的每个函数创建单独的文件并不总是一个好主意。通常,我发现我会创建许多相关的实用函数,以使我的代码更具可读性、可重用性和可组合性;将每个函数放在一个单独的文件中会使代码稍微难以理解,因为 reader 如果不在不同文件之间跳转就无法看到这些函数的定义。

我遇到了同样的问题,找到了一种方法。您可以将 foo.js 文件更改为:

// file: 'foo.js'

export function a() {
   // .....
}

export function b() { 
   let stuff = exports.a(); // using "exports." to call a
   // ...do stuff
}

请参考https://codeburst.io/stub-dependencies-with-sinon-js-259ac12379b9