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

by Michael Szul on

No ads, no tracking, and no data collection. Enjoy this article? Buy us a ☕.

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.