JavaScript

How to unit-test a private (non-exported) function in JavaScript

Advertisements

When writing unit-tests for JavaScript modules, we often encounter a dilemma wherein the module has some private functions that have not been exported. Testing a function that has been exported is easy since it can be imported in the unit testing framework, and the functionality can be tested. But how to unit-test a private (non-exported) function?

Testing exported function

Before we get to the private functions, let us get familiar with how we would test an exported function in JavaScript. We will be using Jest for writing this unit test. Let us assume a function foo such that:

// foo.js
export function foo() {
  return 'bar';
}
JavaScript

We will then write the corresponding unit test case for testing the functionality of the exported function as:

// foo.test.js
import { foo } from './foo.js'

describe('testing foo', () => {
  expect(foo()).toBe('bar');
});
JavaScript

Now that we are familiar with exported functions let us move to the next part. Let us first see what the issue would be first.

Non-exported (private) function

A non-exported function would be something like the secret function in this file:

// foo.js
let secret = () => '🤫'
export function foo() {
  return secret();
}
JavaScript

Now, if we were to test for baz in our unit test,

// foo.test.js
import secret from './foo.js'

// secret is undefined
JavaScript

Note: We should generally not separately test private functions. They are a part of the implementation detail. If a private function behaves incorrectly, then the publicly exposed functions which make use of it would misbehave as well. This way, by testing the public interface, we are also testing the private function. But there are scenarios like the private function is too complex where this might be needed. Another scenario could be that multiple private functions are being invoked one after another in a public function, and we need to test them individually.

Now that we know what the problem is, let us think of potential solutions.

The most obvious one would be to not have “secret” as a private function and export it from the module foo. Though it is a quick way of resolving this problem, it is not the right way to do it. We are exposing a lot of functionality for the sake of unit testing our module, which can bring in a lot of security risks with it.

Introducing Rewire

Having written unit-tests in other languages, I knew there should be a better solution out there. I was looking for something that would let me keep the private functions as private members of the module but still make them testable. And Rewire provided me with this functionality.

Though Rewire was introduced as a monkey patching solution for unit-tests, the official description states:

“Rewire adds a special setter and getter to modules so you can modify their behavior for better unit testing. You may:

  • inject mocks for other modules or globals like a process
  • inspect private variables
  • override variables within the module.”

And the second point is what we need to solve our problem!

Rewire and ES6

The common js implementation of rewire does not have ES6 support. And since we are using ES6 imports in our project, we want to use a package that transfers the concepts to ES6. And that is where the babel plugin for rewire comes into play.

babel-plugin-rewire is inspired by rewire.js and transfers the concepts to ES6 using Babel. So we will install it in our project using npm/yarn:

npm install babel-plugin-rewire --save-dev
Bash

or

yarn add --dev babel-plugin-rewire
Bash

And in the babel config file, we need to import it:

// babel.config.js
module.exports = {
  plugins: ['babel-plugin-rewire'],
  ...
};
JavaScript

Unit-test a private function

Now that we have Rewire in place using the babel-plugin, we can access the private/non-exported function using the __get__ method. This method on the module acts as a getter allowing us to pull out the private function:

// foo.test.js
import foo from './foo.js'

describe('testing foo', () => {
  const secret = foo.__get__('secret'); // rewire magic happens here
  expect(secret()).toBe('🤫');
});
JavaScript

And thus, we can have our cake and eat it too. We are magically able to call private functions without exporting them from the module. We no longer need to resort to hacks to get references to the non-exported JavaScript functions and can ensure that the function remains private.

We hope this post helped you understand how to unit-test a private (non-exported) function in JavaScript using Rewire. The method discussed above works not just for Jest but for other unit-testing frameworks as well. And we do not need to do any code refactors or export our private functions just for the sake of testing. You might also be interested in our running a specific unit-test case in Jasmine blog post if you are writing unit-test cases for JavaScript.

If there is something that you would like to add to the post or something that you think we should cover next, do leave a comment below telling us about it.

Saransh Kataria

Born in Delhi, India, Saransh Kataria is the brain behind Wisdom Geek. Currently, Saransh is a software developer at a reputed firm in Austin, and he likes playing with new technologies to explore different possibilities. He holds an engineering degree in Computer Science. He also shares his passion for sharing knowledge as the community lead at Facebook Developer Circle Delhi, NCR which is a developer community in Delhi, India.

Share
Published by
Saransh Kataria

Recent Posts

Remapping keyboard keys to avoid Carpal Tunnel

I am terrible at optimizing my keyboard layout for anything. But off lately, my little…

1 month ago

Fixing cookies are blocked for a website with shields down on Brave

I recently switched completely to the Brave browser and have set ad blocking to aggressive…

5 months ago

Generating a QR code using Node.js

I was preparing a slide deck for a hackathon and decided to put in a…

6 months ago

How to clear the global npx cache

I have been using npx a lot lately, especially whenever I want to use a…

6 months ago

Copy/Pasting output from the terminal

Manually copy-pasting the output of a terminal command with a mouse/trackpad feels tedious. It is…

7 months ago

How To Get The Hash of A File In Node.js

While working on a project, I wanted to do an integrity check of a file…

8 months ago
Advertisements