【问题标题】:Sinon stub a function defined in the same fileSinon stub 同一个文件中定义的函数
【发布时间】:2022-01-27 05:35:49
【问题描述】:

我的代码如下:

// example.js
export function doSomething() {
  if (!testForConditionA()) {
    return;
  }

  performATask();
}

export function testForConditionA() {
  // tests for something and returns true/false
  // let's say this function hits a service or a database and can't be run in tests 
  ...
}

export function performATask() {
  ...
}


// example.test.js
import * as example from 'example';

it('validates performATask() runs when testForConditionA() is true', () => {
  const testForConditionAStub = sinon.stub(example, 'testForConditionA').returns(true);
  const performATaskSpy = sinon.stub(example, 'performATask');

  example.doSomething();
  expect(performATaskSpy.called).to.equal(true);
});

(我知道,这是一个人为的例子,但我尽量保持简短)

我还没有找到使用 Sinon 模拟 testForConditionA() 的方法。

我知道有一些变通办法,比如

A) 将 example.js 中的所有内容放入一个类中,然后可以对类的函数进行存根。

B) 将 testForConditionA() (和其他依赖项)从 example.js 中移出到一个新文件中,然后使用 proxyquire

C) 将依赖项注入 doSomething()

但是,这些选项都不可行 - 我在一个大型代码库中工作,许多文件需要重写和大修。我已经搜索过这个主题,我看到了其他几篇文章,比如Stubbing method in same file using Sinon,但是除了将代码重构到一个单独的类(或一个人建议的工厂),或者重构到一个单独的文件并使用 proxyquire,我还没有找到解决方案。我以前使用过其他的测试和模拟库,所以很奇怪Sinon 不能做到这一点。或者是吗?关于如何在不重构要测试的代码的情况下对函数进行存根的任何建议?

【问题讨论】:

  • 嗨,杰夫。如果我的回答在某些方面有帮助,请不要忘记点赞按钮或“接受回答”复选标记 ;)
  • 您还有没有回答的问题?

标签: javascript unit-testing sinon


【解决方案1】:

来自a very related answer(我的)的这一点说明了为什么它并不那么令人惊讶:

ES模块默认是不可变的,也就是说Sinon不能做zilch。

EcmaScript 规范规定了这一点,因此当前唯一改变导出的方法是让运行时不遵守规范。这基本上就是 Jest 所做的:它提供自己的运行时,将导入调用转换为等效的 CJS 调用 (require) 调用,并在该运行时中提供自己的 require 实现,该实现与加载过程挂钩。生成的“模块”通常具有可以覆盖的可变导出(即存根)。

Jest 也不支持原生 ESM(如不编译/修改源代码)。跟踪问题48429430 了解这有多复杂(需要更改节点)。

所以,不,诗乃自己无法做到这一点。它只是一个存根库。它不会触及运行时或做任何神奇的事情,因为它必须不受环境影响。


现在回到你原来的问题:测试你的模块。我看到这种情况发生的唯一方法是通过某种依赖注入机制(您在替代 C 中涉及)。显然,您的模块依赖于某些(内部/外部)状态,这意味着您需要一种方法来从外部更改该状态或注入测试替身(您正在尝试什么)。

一种简单的方法是创建一个严格用于测试的 setter:

function callNetworkService(...args){
   // do something slow or brittle
}

let _doTestForConditionA = callNetworkService;

export function __setDoTestForConditionA(fn){
   _doTestForConditionA = fn;
}

export function __reset(){
   _doTestForConditionA = callNetworkService;
}

export function testForConditionA(...args) {
   return _doTestForConditionA(...args);
}

然后您可以像这样简单地测试您的模块:

afterEach(() => {
    example.__reset();
});

test('that my module calls the outside and return X', async () => {
    const fake = sinon.fake.resolves({result: 42});
    example.__setDoTestForConditionA(fake);
    const pendingPromise = example.doSomething();
    expect(fake.called).to.equal(true);

    expect((await pendingPromise).result).toEqual(42);
});

是的,您确实修改了您的 SUT 以允许测试,但我从未发现这一切令人反感。无论框架(Jasmine、Mocha、Jest)或运行时(浏览器、Node、JVM)如何,该技术都能正常工作并且读取良好。

可选注入的依赖项

您确实提到了将依赖项注入到函数中,实际上取决于它们,这有一些问题会传播到整个代码库。

我想通过展示我过去使用过的一种技术来挑战这一点。在 Sinon 问题跟踪器上查看此评论(由我撰写):https://github.com/sinonjs/sinon/issues/831#issuecomment-198081263

我使用这个例子来展示如何在构造函数中注入存根,而构造函数的普通使用者不需要关心这些存根。当然,确实要求您使用某种Object 来不添加其他参数。

/**
 * Request proxy to intercept and cache outgoing http requests
 *
 * @param {Number} opts.maxAgeInSeconds how long a cached response should be valid before being refreshed
 * @param {Number} opts.maxStaleInSeconds how long we are willing to use a stale cache in case of failing service requests
 * @param {boolean} opts.useInMemCache default is false
 * @param {Object} opts.stubs for dependency injection in unit tests
 * @constructor
 */
function RequestCacher (opts) {
    opts = opts || {};
    this.maxAge = opts.maxAgeInSeconds || 60 * 60;
    this.maxStale = opts.maxStaleInSeconds || 0;
    this.useInMemCache = !!opts.useInMemCache;
    this.useBasicToken = !!opts.useBasicToken;
    this.useBearerToken = !!opts.useBearerToken;

    if (!opts.stubs) {
        opts.stubs = {};
    }

    this._redisCache = opts.stubs.redisCache || require('./redis-cache');
    this._externalRequest = opts.stubs.externalRequest || require('../request-helpers/external-request-handler');
    this._memCache = opts.stubs.memCache || SimpleMemCache.getSharedInstance();
}

(查看扩展 cmets 的问题跟踪器)

没有什么强迫任何人提供存根,但是测试可以提供它们来覆盖依赖项的工作方式。

【讨论】:

    猜你喜欢
    • 2017-03-23
    • 2013-12-31
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 2016-05-22
    • 2023-02-22
    • 2018-12-31
    • 1970-01-01
    相关资源
    最近更新 更多