【问题标题】:Stubbing functions used by other functions within es6 moduleses6 模块中其他函数使用的存根函数
【发布时间】:2017-01-15 01:27:15
【问题描述】:

我一直在考虑如何测试同一文件中其他函数使用的特定函数。

例如,假设我在一个文件中有两个函数:

// validators.js
export const required = ...;
export const containsUppercase = ...;
export const containsNumber = ...;

export const password = (val) => [required, containsUppercase, containsNumber].every(f => f(val));

很明显,密码验证器取决于requiredcontainsUppercasecontainsNumber 函数。但是,如果我想将它们存根怎么办,所以我的测试实际上不必关心这些函数或它们如何验证。

这是一个简单而愚蠢的例子,但我在更复杂的场景中遇到了同样的问题。

例如,如果我尝试使用 sinon 执行此操作,我可能会尝试执行此操作:

import * as validators from './validators';

sinon.stub(validators, 'required').returns(false);
sinon.stub(validators, 'containsUppercase').returns(true);
sinon.stub(validators, 'containsNumber').returns(true);

// test
expect(validators.password('notImportant')).toBe(false);

我试过这样做,但我无法让它工作。我发现我可以让它工作,如果我做这样的事情,虽然我还没有测试过。

const required = ...;
const containsUppercase = ...;
const containsNumber = ...;

const password = (val) => [ex.required, ex.containsUppercase, ex.containsNumber].every(f => f(val));

export default ex = {
  required,
  containsUppercase,
  containsNumber,
  password,
}

这个解决方案也很丑,因为我现在不得不担心将函数导出到正确的位置,并且还要通过导出对象引用它。

有没有更清洁的方法来做到这一点?或者更确切地说,测试这些功能的最佳方法是什么?

【问题讨论】:

  • 查看rewire编辑:没关系,rewireconst 有问题。
  • 哦,真令人失望。我一直在看 babel-plugin-rewire,因为从文档来看,rewire 本身似乎不适用于 es6。但是如果它不支持const,那就不行了。
  • 不过,你可以试试看babel-plugin-rewire 是否有效。
  • 可测试性是使用 IoC/DI 的一个很好的理由,例如 this library(深受 Angular 2 DI 的启发)。
  • 这真的很酷,但不幸的是,我不使用打字稿。另外,我对依赖注入非常熟悉,但每次都必须调用password(requiredFn, uppercaseFn, numberFn, val) 对我来说似乎不是很吸引人。我也考虑过更改函数以允许部分应用:const password = (required, uppercase, number) => val => ...,但是对于这样一个简单的示例来说,这会增加很多复杂性,所以它似乎不可行。

标签: javascript testing ecmascript-6 sinon


【解决方案1】:

第二个 sn-p 中的模式是正确的。 password 应该引用对象方法,以便在 validators.js 范围之外模拟它们。

在 JS 中自然实现这种行为的方法是使用this

export class Validators {
  required = (...) => { ... }
  ...
  password = (val) => [this.required, ...].every(...);
}

export default new Validators;

或者,使用正常(非箭头)功能:

export default {
  required(...) {
    ...
  }
  ...
  password(val) {
    [this.required, ...].every(...);
  }
}

在第二种情况下,required 等方法如果在像[this.required, ...].every(...) 一样被调用时使用this,则应额外绑定。

【讨论】:

  • 这似乎是一种潜在可行的方法。我仍然需要在前面加上this.,但它比我发现的要好。谢谢!
  • 当然,this 是一些 OOP 优点的固定价格。一些 Node/CommonJS 包引用 exports 属性的目的与您使用 ex 的目的相同,但正如您所见,这种模式还不够好 ES6 模块。
【解决方案2】:

“但每次都必须调用 password(requiredFn, uppercaseFn, numberFn, val) 对我来说似乎不是很吸引人。”

您可以使用参数的默认值:

const defaultValidators = [required, containsUppercase, containsNumber];
const password = (value, validators = defaultValidators) => validators.every(f => f(value));

这允许您同时调用password('pass')password('pass', [v => false]),从而存根您的验证函数。

【讨论】:

  • 默认参数让我完全忘记了。谢谢你提醒我!尽管如此,拥有那些只会被测试使用的额外参数似乎毫无意义。我可能仍然更喜欢我的部分应用解决方案。依赖注入在理智范围内很好。这与理智接近。
猜你喜欢
  • 2018-11-18
  • 1970-01-01
  • 2017-07-07
  • 2023-01-28
  • 1970-01-01
  • 2017-03-06
  • 1970-01-01
  • 2021-01-24
  • 1970-01-01
相关资源
最近更新 更多