I'm trying to figure out on how to test internal (i.e. not exported) functions in nodejs (preferably with mocha or jasmine). And i have no idea!
Let say I have a module like that:
function exported(i) {
return notExported(i) + 1;
}
function notExported(i) {
return i*2;
}
exports.exported = exported;
And the following test (mocha):
var assert = require('assert'),
test = require('../modules/core/test');
describe('test', function(){
describe('#exported(i)', function(){
it('should return (i*2)+1 for any given i', function(){
assert.equal(3, test.exported(1));
assert.equal(5, test.exported(2));
});
});
});
Is there any way to unit test the notExported function without actually exporting it since it's not meant to be exposed?
Regards,
Xavier
EDIT:
Loading a module using vm can cause unexpected behavior (e.g. the instanceof operator no longer works with objects that are created in such a module because the global prototypes are different from those used in module loaded normally with require). I no longer use the below technique and instead use the rewire module. It works wonderfully. Here's my original answer:
Elaborating on srosh's answer...
It feels a bit hacky, but I wrote a simple "test_utils.js" module that should allow you to do what you want without having conditional exports in your application modules:
var Script = require('vm').Script,
fs = require('fs'),
path = require('path'),
mod = require('module');
exports.expose = function(filePath) {
filePath = path.resolve(__dirname, filePath);
var src = fs.readFileSync(filePath, 'utf8');
var context = {
parent: module.parent, paths: module.paths,
console: console, exports: {}};
context.module = context;
context.require = function (file){
return mod.prototype.require.call(context, file);};
(new Script(src)).runInNewContext(context);
return context;};
There are some more things that are included in a node module's gobal module object that might also need to go into the context object above, but this is the minimum set that I need for it to work.
Here's an example using mocha BDD:
var util = require('./test_utils.js'),
assert = require('assert');
var appModule = util.expose('/path/to/module/modName.js');
describe('appModule', function(){
it('should test notExposed', function(){
assert.equal(6, appModule.notExported(3));
});
});
The trick is to set the NODE_ENV environment variable to something like test and then conditionally export it.
Assuming you've not globally installed mocha, you could have a Makefile in the root of your app directory that contains the following:
REPORTER = dot
test:
@NODE_ENV=test ./node_modules/.bin/mocha \
--recursive --reporter $(REPORTER) --ui bbd
.PHONY: test
This make file sets up the NODE_ENV before running mocha. You can then run your mocha tests with make test at the command line.
Now, you can conditionally export your function that isn't usually exported only when your mocha tests are running:
function exported(i) {
return notExported(i) + 1;
}
function notExported(i) {
return i*2;
}
if (process.env.NODE_ENV === "test") {
exports.notExported = notExported;
}
exports.exported = exported;
The other answer suggested using a vm module to evaluate the file, but this doesn't work and throws an error stating that exports is not defined.
The rewire module is definitely the answer.
Here's my code for accessing an unexported function and testing it using Mocha.
application.js:
function logMongoError(){
console.error('MongoDB Connection Error. Please make sure that MongoDB is running.');
}
test.js:
var rewire = require('rewire');
var chai = require('chai');
var should = chai.should();
var app = rewire('../application/application.js');
logError = app.__get__('logMongoError');
describe('Application module', function() {
it('should output the correct error', function(done) {
logError().should.equal('MongoDB Connection Error. Please make sure that MongoDB is running.');
done();
});
});
you can make a new context using vm module and eval the js file in it, sort of like repl does. then you have access to everything it declares.
I have found a quite simple way that allows you to test, spy and mock those internal functions from within the tests:
Let's say we have a node module like this:
mymodule.js:
------------
"use strict";
function myInternalFn() {
}
function myExportableFn() {
myInternalFn();
}
exports.myExportableFn = myExportableFn;
If we now want to test and spy and mock myInternalFn while not exporting it in production we have to improve the file like this:
my_modified_module.js:
----------------------
"use strict";
var testable; // <-- this is new
function myInternalFn() {
}
function myExportableFn() {
testable.myInternalFn(); // <-- this has changed
}
exports.myExportableFn = myExportableFn;
// the following part is new
if( typeof jasmine !== "undefined" ) {
testable = exports;
} else {
testable = {};
}
testable.myInternalFn = myInternalFn;
Now you can test, spy and mock myInternalFn everywhere where you use it as testable.myInternalFn and in production it is not exported.