Unit testing of private functions with Mocha and Node.js
Solution 1
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.
Solution 2
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).....
Solution 3
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"
Solution 4
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
}
Solution 5
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;
Related videos on Youtube
Comments
-
fstab over 3 years
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?
-
dskrvk about 7 yearsRelated: stackoverflow.com/questions/14874208
-
-
Louis over 9 yearsI 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 withtextExports
is loaded by arequire
call beforerequireFrom
loads it,requireFrom
will returnundefined
. (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 over 9 yearsI'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.
-
jemiloii over 9 yearsIt 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.
-
Louis over 8 years@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.
-
jemiloii over 8 yearsIt 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.
-
aij over 8 yearsConditionally 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
-
Jason about 8 yearsyou can also use nested syntax { ... private : { worker : worker } }
-
cchamberlain about 8 years@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 usemodule.exports = process.env.NODE_ENV === 'production' ? require('prod.js') : require('dev.js')
and store your es6 code differences in those respective files. -
Louis about 7 years@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.
-
Mike Stead about 7 yearsJust 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.
-
user2918201 almost 7 yearsIf the module is all pure functions, then I see no downside to doing this. If you are keeping and mutating state, then beware...
-
user2918201 almost 7 yearsI guess that if you have full coverage, then you are testing all your private functions, whether you've exposed them or not.
-
btburton42 almost 7 yearsRewire 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.
-
JRulle over 6 yearsAlso found a webkit plugin that looks like it can support a similar workflow: webpack-strip-block
-
shoke over 6 yearsSo 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? -
muthukumar selvaraj over 6 yearsrewire is working fine in
.ts
,typescript
i run usingts-node
@clu -
RayLoveless almost 5 years@aij You can conditionally export... see this answer: stackoverflow.com/questions/39583958/…
-
RayLoveless over 4 yearsI believe this will get skipped if you use f2 to rename your function in vsCode.
-
Peter Mortensen over 3 yearsAn explanation would be in order. For instance, how and in which context is environment variable
test
set?