使用 Mocha 和 Node.js 对私有函数进行单元测试

我使用 摩卡是为了单元测试为 Node.js 编写的应用程序。

我想知道是否有可能对模块中没有导出的函数进行单元测试。

例如:

我在 foobar.js中定义了很多这样的函数:

function private_foobar1(){
...
}


function private_foobar2(){
...
}

以及一些导出为 public 的函数:

exports.public_foobar3 = function(){
...
}

测试用例的结构如下:

describe("private_foobar1", function() {
it("should do stuff", function(done) {
var stuff = foobar.private_foobar1(filter);
should(stuff).be.ok;
should(stuff).....

显然,这不起作用,因为没有导出 private_foobar1

什么是单元测试私有方法的正确方法? Mocha 是否有一些内置的方法来做到这一点?

73185 次浏览

如果该函数未被模块导出,则不能由模块外部的测试代码调用该函数。这是由于 JavaScript 的工作方式,而 Mocha 本身无法绕过这个问题。

在少数情况下,我确定测试私有函数是正确的事情,我设置了一些环境变量,我的模块检查它是否在测试设置中运行。如果它在测试设置中运行,那么它将导出额外的函数,然后我可以在测试期间调用这些函数。

“环境”这个词在这里用得很随意。这可能意味着检查 process.env或其他可以与模块“您正在进行测试”进行通信的内容。我必须这样做的实例是在 需要环境中,为此目的我使用了 module.config

我为此制作了一个 npm 包,您可能会发现它很有用: 需求

基本上,您可以通过以下方式公开非公共方法:

module.testExports = {
private_foobar1: private_foobar1,
private_foobar2: private_foobar2,
...
}

注意: testExports可以是任何您想要的有效名称,当然除了 exports

来自另一个模块:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;

如果您希望保持简单,那么也可以导出私有成员,但是要用一些约定将它们与公共 API 清楚地分开,例如,在它们的前缀使用 _,或者将它们嵌套在单个 二等兵对象之下。

var privateWorker = function() {
return 1
}


var doSomething = function() {
return privateWorker()
}


module.exports = {
doSomething: doSomething,
_privateWorker: privateWorker
}

看看 重新接线模块,它允许你在一个模块中获取(和操作)私有变量和函数。

所以在你的例子中,用法是这样的:

var rewire = require('rewire'),
foobar = rewire('./foobar'); // Bring your module in with rewire


describe("private_foobar1", function() {


// Use the special '__get__' accessor to get your private function.
var private_foobar1 = foobar.__get__('private_foobar1');


it("should do stuff", function(done) {
var stuff = private_foobar1(filter);
should(stuff).be.ok;
should(stuff).....

我跟踪了 Barwin 的回答并检查了如何使用 重新接线模块进行单元测试。我可以确定这个解决方案是有效的。

该模块应该分为两个部分-一个公共部分和一个私有部分。对于公共职能,你可以采用标准方法:

const { public_foobar3 } = require('./foobar');

私人范围:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

为了更多地了解这个主题,我创建了一个具有完整模块测试的工作示例,测试包括私有和公共作用域。

为了获得更多的信息,我建议您查看完整描述该主题的文章(如何测试 CommonJS 模块的私有函数) ,其中包括代码示例。

我已经添加了一个额外的函数,我命名为 内部()并从那里返回所有私有函数。然后导出这个 内部()函数。例如:

function Internal () {
return { Private_Function1, Private_Function2, Private_Function2}
}


// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

你可以这样调用内部函数:

let test = require('.....')
test.Internal().Private_Function1()

我最喜欢这个解决方案,因为:

  • 只有一个函数 内部()总是被导出。这个 内部()函数总是被用来测试私有函数。
  • 实现起来很简单
  • 对生产代码的影响很小(只有一个额外的功能)

我知道这不一定是您想要的答案,但是我发现大多数情况下,如果私有函数值得测试,那么它就值得放在它自己的文件中。

例如,与公共文件中的私有方法不同的是..。

Src/thing/PublicInterface.js


function helper1 (x) {
return 2 * x;
}


function helper2 (x) {
return 3 * x;
}


export function publicMethod1(x) {
return helper1(x);
}


export function publicMethod2(x) {
return helper1(x) + helper2(x);
}

然后像这样分开:

Src/thing/PublicInterface.js

import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';


export function publicMethod1(x) {
return helper1(x);
}


export function publicMethod2(x) {
return helper1(x) + helper2(x);
}

Src/thing/Internal/helper1.js

export function helper1 (x) {
return 2 * x;
}

Src/thing/Internal/helper2.js

export function helper2 (x) {
return 3 * x;
}

这样,你就可以很容易地测试 helper1helper2的原样,而不需要使用 Rewire 和其他“魔法”(我发现,在调试的时候,或者当你尝试使用 TypeScript 的时候,它们都有自己的痛处,更不用说对新同事的理解能力更差了)。它们在一个叫做 internal的子文件夹中,或者类似的东西,将有助于避免意外地在意想不到的地方使用它们。


附注: “ private”方法的另一个常见问题是,如果您想测试 publicMethod1publicMethod2并模拟助手,通常还需要使用 Rewire 之类的东西来完成。但是,如果它们在单独的文件中,您可以使用 Proxyquire来完成,与 Rewire 不同,Proxyquire不需要对构建过程进行任何更改,易于读取和调试,甚至与 TypeScript 一起工作也很好。

为了使私有方法可用于测试,我这样做:

const _myPrivateMethod: () => {};


const methods = {
myPublicMethod1: () => {},
myPublicMethod2: () => {},
}


if (process.env.NODE_ENV === 'test') {
methods._myPrivateMethod = _myPrivateMethod;
}


module.exports = methods;