hckr.fyi // thoughts

Unit Testing Private (Non-Exported) Functions in JavaScript w/ Rewire

by Michael Szul on

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.