javascript - Sinon stub 定义在同一文件中的函数

标签 javascript unit-testing sinon

我的代码如下:

// 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 中的所有内容放入一个类中,然后可以 stub 该类的函数。

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

C) 将依赖项注入(inject) doSomething()

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

最佳答案

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

ES modules are not mutable by default, which means Sinon can't do zilch.

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

Jest 也不支持原生(因为没有源代码的转译/修改)ESM。跟踪问题 48429430这有多复杂(需要更改 Node)。

所以,不,诗乃自己是做不到的。它只是一个 stub 库。它不会触及运行时或做任何神奇的事情,因为它必须在任何环境下都能正常工作。


现在回到您最初的问题:测试您的模块。我看到这种情况发生的唯一方法是通过某种依赖注入(inject)机制(您在备选方案 C 中提到过)。您显然有一些(内部/外部)状态是您的模块所依赖的,因此这意味着您需要一种从外部更改该状态或注入(inject)测试替身(您正在尝试的)的方法。

一个简单的方法就是创建一个严格用于测试的 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)如何,该技术都适用,并且读起来很好。

选择性地注入(inject)依赖

您确实提到将依赖项注入(inject)到实际上依赖于它们的函数中,这会产生一些会传播到整个代码库的问题。

我想通过展示我过去使用过的技术来挑战一下。请参阅我在 Sinon 问题跟踪器上的评论:https://github.com/sinonjs/sinon/issues/831#issuecomment-198081263

我使用这个示例来展示如何在构造函数中注入(inject) stub ,而该构造函数的普通使用者无需关心这些 stub 。当然,确实需要您使用某种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();
}

(请参阅问题跟踪器以获取更多评论)

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

关于javascript - Sinon stub 定义在同一文件中的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70873822/

相关文章:

unit-testing - Emberjs 测试组件的 Action 委托(delegate)

node.js - 使用 mocha+sinon 测试 ExpressJS 路由时,如何 "stub"路由本地函数?

javascript - 如何导入 SVG 文件以便访问其属性?

javascript - 使用 javascript :history. go(-1) 有效,但我丢失了 css 属性和脚本

spring - 单元测试 Spring Cloud 服务的策略

c# - 我应该如何检查工厂在单元测试中创建的对象图

c++ - 按类别对 googletest 单元测试进行分组

javascript - 尝试对齐视频时的典型 IE 问题 - 请指教

javascript - 尝试删除 HTMLElement 的原型(prototype)失败

javascript - Sinon Fake 服务器不自动响应