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

fst*_*tab 125 javascript unit-testing private mocha.js node.js

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

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

例:

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

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

exports.public_foobar3 = function(){
    ...
}
Run Code Online (Sandbox Code Playgroud)

测试用例的结构如下:

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

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

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

小智 183

检查重新布线模块.它允许您获取(和操作)模块中的私有变量和函数.

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

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).....
Run Code Online (Sandbox Code Playgroud)

  • @Jaro我的大多数代码都是AMD模块的形式,重新连接无法[handle](https://github.com/jhnns/rewire#limitations)(因为AMD模块是函数但重新连接无法处理"变量"在功能").或者被翻译,另一种重新连线无法处理的场景.实际上,那些打算重新连接的人在尝试使用它之前首先阅读这些限制(之前已经链接过)会很好.我没有一个应用程序,a)需要输出"私人"的东西和b)不会遇到重新连接的限制. (3认同)

Lou*_*uis 60

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

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

这里使用了"环境"这个词.这可能意味着检查process.env或其他可以与模块通信的内容"您现在正在测试".我必须这样做的实例是在RequireJS环境中,我已经用于module.config此目的.

  • 有条件地导出值似乎与ES6模块不兼容。我收到“ SyntaxError:'导入'和'导出'可能仅出现在顶层” (2认同)
  • 我想如果你有完整的覆盖,那么你正在测试你所有的私有函数,不管你是否公开了它们。 (2认同)

Rém*_*ras 24

这是一个非常好的工作流程来测试您的私人方法,这是由Google工程师Philip Walton在其博客上解释的.

原理

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

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

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

片段

把你的代码写成:

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
}())
Run Code Online (Sandbox Code Playgroud)

还有那样的笨拙的任务

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])
Run Code Online (Sandbox Code Playgroud)

更深入

在后面的文章中,它解释了"测试私有方法"的"原因"


fam*_*kin 21

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

var privateWorker = function() {
    return 1
}

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

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}
Run Code Online (Sandbox Code Playgroud)

  • 我已经完成了这个过程,因为整个模块真的是私有的而不是普通的消费.但是对于通用模块,我更喜欢在测试代码时公开我需要测试**.确实,最终没有任何东西会阻止某人通过伪造测试环境来获取私有内容但是当一个人在他们自己的应用程序上进行调试时,我宁愿他们看不到那些不需要的符号.部分公共API.通过这种方式,没有立即将API滥用于其不适合的目的. (6认同)
  • 您还可以使用嵌套语法 { ... __private__ : { worker : worker } } (2认同)
  • 如果模块都是纯函数,那么我认为这样做没有任何缺点.如果你要保持和改变状态,那么要小心...... (2认同)

Per*_*erk 6

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

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

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }
Run Code Online (Sandbox Code Playgroud)

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

let test = require('.....')
test.Internal().Private_Function1()
Run Code Online (Sandbox Code Playgroud)

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

  • 只有一个函数Internal()总是被导出。此Internal()函数始终用于测试私有函数。
  • 实施起来很简单
  • 对生产代码影响小(只有一项额外功能)


小智 5

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

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

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

从另一个模块:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;
Run Code Online (Sandbox Code Playgroud)

  • 我认为这种方法没有任何实际优势。它不会使“私有”符号变得更加私有。(任何人都可以使用正确的参数调用“requireFrom”。)此外,如果在“requireFrom”加载之前通过“require”调用加载带有“textExports”的模块,则“requireFrom”将返回“undefined”。(我刚刚测试过。)虽然通常可以控制模块的加载顺序,但并不总是实用。(正如关于 SO 的一些 Mocha 问题所证明的那样。)该解决方案通常也不适用于 AMD 类型的模块。(我每天在 Node 中加载 AMD 模块进行测试。) (2认同)