160

I am using Mocha in order to unit test an application written for Node.js.

I wonder if it's possible to unit test functions that have not been exported in a module.

Example:

I have a lot of functions defined like this in foobar.js:

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

And a few functions exported as public:

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

The test case is structured as follows:

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

Obviously this does not work, since private_foobar1 is not exported.

What is the correct way to unit-test private methods? Does Mocha have some built-in methods for doing that?

1

11 Answers 11

200

Check out the rewire module. It allows you to get (and manipulate) private variables and functions within a module.

So in your case the usage would be something like:

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).....
6
  • 3
    @Jaro Most of my code is either in the form of AMD modules, which rewire is unable to handle (because AMD modules are functions but rewire cannot handle "variables within functions"). Or is transpiled, another scenario which rewire cannot handle. Actually, people who are going to look at rewire would do well to first read the limitations (linked earlier) before they try to use it. I don't have a single app that a) needs exporting "private" stuff and b) does not run into a limitation of rewire.
    – Louis
    Commented Apr 5, 2017 at 10:25
  • 1
    Just a small point, code coverage may fail to pick up tests written like this. At least that's what I've seen using Jest's in-built coverage tool.
    – uxcxdx
    Commented Apr 17, 2017 at 8:36
  • Rewire does not play well with jest's auto-mocking tool, either. I am still looking for a way to leverage jest's benefits and access some private vars.
    – btburton42
    Commented Sep 27, 2017 at 17:16
  • So I tried making this work but I'm using typescript, which I'm guessing is causing this issue. Basically I get the following error: Cannot find module '../../package' from 'node.js'. Anyone familiar with this?
    – b.lyte
    Commented Nov 28, 2017 at 7:45
  • rewire is working fine in .ts , typescript i run using ts-node @clu Commented Jan 19, 2018 at 9:07
84

If the function is not exported by the module, it cannot be called by test code outside the module. That's due to how JavaScript works, and Mocha cannot by itself circumvent this.

In the few instances where I determined that testing a private function is the right thing to do, I've set some environment variable that my module checks to determine whether it is running in a test setup or not. If it runs in the test setup, then it exports additional functions that I can then call during testing.

The word "environment" is loosely used here. It might mean checking process.env or something else that can communicate to the module "you're being tested now". The instances where I've had to do this were in a RequireJS environment, and I've used module.config for this purpose.

4
  • 4
    Conditionally exporting values does not appear to be compatible with ES6 modules. I'm getting SyntaxError: 'import' and 'export' may only appear at the top level
    – aij
    Commented Feb 29, 2016 at 16:46
  • 1
    @aij yes due to ES6 static exports you cannot use import, export inside of a block. Eventually you will be able to accomplish this sort of thing in ES6 with the System loader. One way to get around it now is to use module.exports = process.env.NODE_ENV === 'production' ? require('prod.js') : require('dev.js') and store your es6 code differences in those respective files. Commented Jun 1, 2016 at 1:51
  • 2
    I guess that if you have full coverage, then you are testing all your private functions, whether you've exposed them or not.
    – Ziggy
    Commented Aug 11, 2017 at 23:37
  • 1
    @aij You can conditionally export... see this answer: stackoverflow.com/questions/39583958/… Commented Aug 14, 2019 at 21:36
30

Here is a really good workflow to test your private methods explained by Philip Walton, a Google engineer on his blog.

Principle

  • Write your code normally
  • Bind your private methods to the object in a separate code block, and mark it by an _ (for example)
  • Surround that code block by start and end comments

Then use a build task or your own build system (for example grunt-strip-code) to strip this block for production builds.

Your tests builds have access to your private API, and your production builds have not.

Snippet

Write your code as this:

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
}())

And your Grunt tasks like this:

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

Deeper

In a later article, it explains the "why" of "testing private methods"

1
  • 1
    Also found a webkit plugin that looks like it can support a similar workflow: webpack-strip-block
    – JRulle
    Commented Oct 3, 2017 at 11:10
23

If you'd prefer to keep it simple, just export the private members as well, but clearly separated from the public API with some convention, e.g. prefix them with an _ or nest them under a single private object.

var privateWorker = function() {
    return 1
}

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

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}
3
  • 8
    I've done this in cases where the entire module is really meant to be private and not for common consumption. But for general-purpose modules I prefer to expose what I need for testing only when the code is being tested. It is true that ultimately there is nothing that would prevent someone from getting to the private stuff by faking a testing environment but when one is doing debugging on their own application, I'd rather they not see the symbols that don't need to be part of the public API. This way there's no immediate temptation to abuse the API for purposes it is not designed for.
    – Louis
    Commented Nov 27, 2014 at 18:01
  • 2
    you can also use nested syntax { ... private : { worker : worker } }
    – Jason
    Commented May 12, 2016 at 0:08
  • 2
    If the module is all pure functions, then I see no downside to doing this. If you are keeping and mutating state, then beware...
    – Ziggy
    Commented Aug 11, 2017 at 23:35
6

I have added an extra function that I name Internal() and return all private functions from there. This Internal() function is then exported. Example:

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

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

You can call the internal functions like this:

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

I like this solution best because:

  • only one function Internal() is always exported. This Internal() function is always used to test private functions.
  • It is simple to implement
  • Low impact on the production code (only one extra function)
6

I made an npm package for this purpose that you might find useful: require-from

Basically, you expose non-public methods by:

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

Note: testExports can be any valid name you want, except exports of course.

And from another module:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;
4
  • 2
    I see no practical advantage to this method. It does not make the "private" symbols more private. (Anybody can call requireFrom with the right parameters.) Also, if the module with textExports is loaded by a require call before requireFrom loads it, requireFrom will return undefined. (I've just tested it.) While it is often possible to control the load order of modules, it's not always practical. (As evidenced by some Mocha questions on SO.) This solution also won't generally work with AMD-type modules. (I load AMD modules in Node on a daily basis for testing.)
    – Louis
    Commented Nov 27, 2014 at 17:54
  • It shouldn't work with AMD modules! Node.js uses common.js and if you changing it to use AMD, then you're doing it out of the norm.
    – jemiloii
    Commented Feb 22, 2015 at 19:48
  • @JemiloII Hundreds of developers use Node.js daily to test AMD modules. There's nothing "out of the norm" in doing that. The most you can say is that Node.js does not come with an AMD loader but this is not saying much, seeing as Node provides explicit hooks to extend its loader to load whatever format developers care to develop.
    – Louis
    Commented Oct 5, 2015 at 16:35
  • It is out of the norm. If you have to manually include an amd loader, it's not the norm for node.js. I rarely see AMD for node.js code. I'll see it for the browser, but node. No. I'm not saying it isn't being done, just the question and this answer we're commenting on, say nothing about amd modules. So without anyone stating that they are using an amd loader, node exports, shouldn't work with amd. Though I do want to note, commonjs might be on its way out with the es6 exports. I just hope that one day we can all just use one export method.
    – jemiloii
    Commented Oct 5, 2015 at 19:07
3

I followed barwin's answer and checked how unit tests can be made with rewire module. I can confirm that this solution simply works.

The module should be required in two parts - a public one and a private one. For public functions you can do that in standard way:

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

For private scope:

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

In order to know more about the subject, I created a working example with full module testing, testing includes private and public scope.

For further information I encourage you to check the article (How to test private functions of a CommonJS module) fully describing the subject. It includes code samples.

0
3

I know that this is not necessarily the answer you are looking for, but I have found that most of the time if a private function is worth testing, it's worth being in its own file.

E.g., instead of having private methods in the same file as the public ones, like this...

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);
}

...you split it up like this:

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;
}

That way, you can easily test helper1 and helper2 as-is, without using Rewire and other "magic" (which, I have found, have their own pain points while debugging, or when you try to make your move towards TypeScript, not to mention poorer understandability for new colleagues). And them being in a sub-folder called internal, or something like that, will help avoiding accidental usage of them in unintended places.


P.S.: Another common issue with "private" methods is that if you want to test publicMethod1 and publicMethod2 and mock the helpers, again, you normally need something like Rewire to do that. However, if they are in separate files, you can use Proxyquire to do it, which, unlike Rewire, doesn't need any changes to your build process, is easy to read and to debug, and works well even with TypeScript.

2

To make private methods available for testing, I do this:

const _myPrivateMethod: () => {};

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

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

module.exports = methods;
1
  • 1
    An explanation would be in order. For instance, how and in which context is environment variable test set? Commented Oct 3, 2020 at 18:15
0

As an option, create a duplicate code with an injection.

Example:

./prod_code.js

export default class A{
  #privateMethod(){
     return 'hello';
  }
}

./test_code.js

    import A from './prod_code.js';

    function inject_method_into_duplicate_сlass(MClass,injectMethodStr){
        let str_Class = MClass.toString();
        let code='return '+MClass.toString().replace (/^[\s]*class[\s]+(\w+)([\s]+extends[\s]+[\w]+)?[\s]*{([\s\S]*)}[\s]*$/,function(str,class_name,extend_class,code){
            return `class ${class_name}${extend_class??''} {\n${injectMethodStr} ${code}}`;
        });
        return Function(code)();
    }

//...
    let Mod_A=inject_method_into_duplicate_сlass(A,'static runPrivateMethod(name,...args){return eval(`this.${name}`)(...args);}')

    assert.ok(Mod_A.runPrivateMethod('#privateMethod')==='hello');

The code is provided as an example. Everyone can come up with their own implementation for the test.

With the help of such injections, the product code will be as clean as possible from the test code.

Updated

But this method has a side effect - all methods, properties and class data initialized behind the class will not be present in the duplicate. therefore, you will have to initialize such properties and methods yourself.

Example

class A{
}
//initialization behind  class
A.prop='hello';
Object.defineProprty(A,'prop2',{
  value:'bay'
});


Updated

There will also be problems with declaring global variables or modules in a class.

import A from './A.js';
 class B extends A // when creating a duplicate, class A must be in scope
{
}
// or 
class B{
   #privateMethod(){
      return new A();// when creating a duplicate, class A must be in scope
   }
}

But such difficulties are solvable.

  1. you create a test file like tests/dublicate_your_class.js
  2. declare the dependencies of the main class in it
  3. dynamically duplicate the class with the necessary injections
  4. Synchronize the properties, methods, and data of the duplicate class with the main class.
  5. Use a duplicate class throughout unit testing.

But the only problem left is if the main class has side effects. Those. if its static data changes during the execution of other code. But in this case, testing such code is not within the purview of unit testing.

0

As is almost always true, I think the best answer to this question is architectural: write your code in a way that is NATIVELY testable, without requiring additional dependencies that won't go into production.

If you are writing ES6 code, then your default ES6 module export should be your ES6 source. You shouldn't NEED transpiling.

In this case, do the following:

  • Create a "private" class whose testable methods are public (therefore accessible to your unit test test framework) but are NOT exported from your package.

  • Include this class by composition (not inheritance) into a "public" wrapper class that IS exported from your package.

Now you have the best possible scenario:

  • You can test any and every class method AS IS, without adding any new dependencies or modifying the tested code before publication.
  • You have complete control over which methods the wrapper class exposes.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.