Unit Testing Private (Non-Exported) Functions in JavaScript w/ Rewire
I'm not a huge fan of test-driven development, but that doesn't mean that I don't value unit testing. Unit testing alone won't bake quality into your code. You can write 50 unit tests, but only cover 10-20% of your code base. This is why code coverage--and code coverage tools like Istanbul's NYC--are so important. Code coverage tools force you to uncouple functionality in order to get a high coverage percentage, but it can be a beast to try to cover all statements, branches, and lines.
In JavaScript, your modules can have both exported and non-exported functions. How do you test a non-exported function (essentially a private function) in your test script, if you can't import the function?
There is a fantastic module called Rewire that helps you do just that.
Imagine you have a function in a utility file (util.js
) for shaping data. You receive a JSON structure back from an external source, but you have to restructure it to fit a certain output, such as a calendar schema:
export function shapeData(input) {
const time = formatTime(input);
...
}
The time function is not exported, since it's local to the module, and not needed elsewhere:
function formatTime(input) {
...
}
Adding a function like this will drop your code coverage, and depending on your threshold, could put you below the needed coverage for a successful build. But how do you test it, if you can import it into your test script?
Rewire is a fantastic module for dependency injection meant to help you modify the behavior of your code for unit testing. With Rewire you can capture these non-exported methods, and execute them.
For example:
const rewire = require("rewire");
describe("Utility methods for application", () => {
it("should format times", () => {
const util = rewire("./util");
const formatTime = app.__get__("formatTime");
const p = formatTime("08:00AM-12:00PM");
assert.notEqual(p.sh, null);
assert.notEqual(p.sm, null);
assert.notEqual(p.eh, null);
assert.notEqual(p.em, null);
});
it("should fail to get times", () => {
const util = rewire("./util");
const formatTime = app.__get__("formatTime");
assert.throws(() => {
formatTime("");
});
});
});
In the above code, you instantiate Rewire by passing in the JavaScript file you want to test:
const util = rewire("./util");
Once you've instantiated you module, you can use the __get__
method as a getter to pull out the private function:
const formatTime = util.__get__("formatTime");
With this function pulled out, you can execute it just like you would execute the function inside of the module:
const p = formatTime("08:00AM-12:00PM");
This means, you can now run assertion tests against it:
assert.notEqual(p.sh, null);
assert.notEqual(p.sm, null);
assert.notEqual(p.eh, null);
assert.notEqual(p.em, null);
Rewire is a great tool to increase code coverage by allowing you to cover non-exported functions. It also provides a good amount of other functionality for creating mocks and overwriting variables. Check out the NPM module page for more documentation.