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

问题描述 投票:106回答:7

我正在使用mocha来单元测试为node.js编写的应用程序

我想知道是否可以对未在模块中导出的单元测试功能进行单元测试。

例:

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

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

以及一些出口为公共的功能:

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没有出口。

对私有方法进行单元测试的正确方法是什么?摩卡有没有一些内置的方法呢?

javascript node.js unit-testing private mocha
7个回答
58
投票

如果模块未导出该功能,则模块外部的测试代码无法调用该功能。这是由于JavaScript如何工作,而Mocha本身无法绕过这一点。

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

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


177
投票

看看rewire模块。它允许您获取(和操作)模块中的私有变量和函数。

所以在你的情况下,用法将是这样的:

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).....

21
投票

Here is a really good workflow to test your private methods由Google工程师Philip Walton在其博客上解释。

Principle

  • 正常编写代码
  • 将您的私有方法绑定到单独的代码块中的对象,例如用_标记它
  • 通过开始和结束注释围绕代码块

然后使用构建任务或您自己的构建系统(例如grunt-strip-code)来剥离生产构建的集合。

您的测试版本可以访问您的私有API,而您的生产版本则没有。

Snippet

把你的代码写成:

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

还有那样的笨拙的任务

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

More deeper

In a later article,它解释了“测试私人方法”的“原因”


20
投票

如果您希望保持简单,只需导出私有成员,但明确地与公共API分开一些约定,例如用_作为它们的前缀,或者将它们嵌套在一个私有对象下。

var privateWorker = function() {
    return 1
}

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

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

5
投票

我为此目的制作了一个npm包,你可能会发现它很有用:require-from

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

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;

4
投票

我添加了一个额外的函数,我将其命名为Internal()并从那里返回所有私有函数。然后导出此Internal()函数。例:

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

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

您可以像这样调用内部函数:

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

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

  • 始终只导出一个函数Internal()。此Internal()函数始终用于测试私有函数。
  • 它很容易实现
  • 对生产代码的影响很小(只有一个额外的功能)

1
投票

我跟着@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');

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

有关详细信息,我建议您查看完整描述主题的文章(https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f),其中包括代码示例。


0
投票

我知道这不一定是你正在寻找的答案,但我发现大多数时候,如果一个私有函数值得测试,它值得存在于自己的文件中。

例如。而不是像公共文件一样在同一个文件中使用私有方法,比如这样......

SRC /事/ 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 /事/ 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 /事/内部/ helper1.js

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

SRC /事/内部/ helper2.js

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

这样,您可以轻松地测试helper1helper2,而不使用Rewire和其他“魔法”(我发现,在调试时,或者当您尝试向TypeScript迈进时,不是提到新同事的可理解性较差)。它们位于一个名为internal的子文件夹中,或类似的东西,将有助于避免在非预期的地方意外使用它们。


P.S。:“私人”方法的另一个常见问题是,如果你想测试publicMethod1publicMethod2并嘲笑帮助者,你通常需要像Rewire那样做。但是,如果它们位于不同的文件中,您可以使用Proxyquire来执行此操作,与Rewire不同,它不需要对构建过程进行任何更改,易于阅读和调试,并且即使使用TypeScript也能正常工作。


0
投票

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

const _myPrivateMethod: () => {};

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

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

module.exports = methods;
© www.soinside.com 2019 - 2024. All rights reserved.