【问题标题】:Stubbing method in same file using Sinon使用Sinon在同一文件中的存根方法
【发布时间】:2017-02-13 04:09:05
【问题描述】:

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

// 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()

【问题讨论】:

  • 作为一般经验法则,您不应该在您正在测试的同一模块中存根/模拟函数。如果您发现需要这样做,这很好地表明这两个函数实际上应该位于不同的模块中。当然,虽然规则是被打破的,在某些情况下这可能是可以的,但总的来说,你应该避免。
  • @AndrewEisenberg 我目前处于这种情况,并且看到在我正在测试的同一个模块中模拟一个函数仍然有点困难。您能否详细说明为什么我应该将这两个函数拆分为单独的模块?

标签: javascript unit-testing reactjs redux sinon


【解决方案1】:

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

我使用了 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);

输出

模拟返回

1

【讨论】:

  • 你能提供一个例子,当函数 a 和 b 接收一些参数并使用 es6 导出语法时
【解决方案2】:

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

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

在 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'

【讨论】:

    【解决方案3】:

    上述方法(使用factory 收集函数)效果很好;但是,eslint 不喜欢使用尚未声明的变量/函数。因此,我建议稍作修改:

    // 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;
    

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

    【讨论】:

      【解决方案4】:

      我遇到了同样的问题并找到了一种方法。您可以将 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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-08
        • 2015-04-18
        • 2021-06-10
        • 1970-01-01
        • 2018-07-16
        • 2016-10-23
        相关资源
        最近更新 更多