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

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:

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

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');
});

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();
}

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

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

// secret is undefined

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

or

yarn add --dev babel-plugin-rewire

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

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

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('🤫');
});

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.

Recent Articles

Creating a GraphQL API with Apollo Server

One of the many complaints about the GraphQL ecosystem is that there is a lot of indirection around what packages to use...

Natural Language Processing – A 30,000 feet view

Natural Language Processing has gained a lot of popularity owing to an exponential increase in unstructured data generation. Here are some fun...

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

When writing unit-tests for JavaScript modules, we often encounter a dilemma wherein the module has some private functions that have not been...

Detecting click outside component using React hooks

If you have tried developing your own dropdown, modal, or popover in React, you would have come across this. "How do I...

Demystifying Programming Interview: Number of Islands

If you are reading this post right now, chances are high that you are not looking for the number of islands in...

Related Stories

Hi there! Want some more knowledge?

Think that the knowledge shared is helpful? You might want to give our mailing list a try. We'll send you 2-4 emails a month, right when new posts come out.

Hi there! Want some more knowledge?

Think that the knowledge shared is helpful? You might want to give our mailing list a try. We'll send you 2-4 emails a month, right when new posts come out.