34

The ES6 module that I want to test looks as follows:

function privateFunction() {
   ...
}
export function publicFunction() {
   ... does something ...
   privateFunction()
   ... does something else ...
}

I am using JEST for my unit tests and I am trying to find a way to test publicFunction and avoiding the execution of privateFunction by mocking it but I couldn't succeed in the mock attempt. Any idea?

Andreas Köberle
  • 88,409
  • 51
  • 246
  • 277
matteo
  • 1,375
  • 1
  • 12
  • 24
  • Are you asking how to write a test for a private function, or how to mock a private function? – Jordan Running Apr 06 '17 at 21:21
  • Good point. A mocking solution would be enough but both mocking/testing need a solution to access private function from the test. – matteo Apr 06 '17 at 21:35

3 Answers3

21

I found out a way to mock my private function by using the babel-plugin-rewire module.

In package.json I have the following:

  "devDependencies": {
    ...
    "babel-plugin-rewire": "1.0.0-beta-5",
    "babel-jest": "18.0.0",
    ...

In .babel.rc I have the following:

{
  "presets": [
    "es2015",
    "stage-0",
    "react"
  ],
  "env": {
    "test": {
      "plugins": [
        "babel-plugin-rewire"
      ]
    }
  },
  ...

At this point I was able to mock the private function:

import * as moduleToTest from './moduleToTest.js'

describe('#publicFunction', () => {
  it('mocks private function', () => {
    moduleToTest.__Rewire__('privateFunction', () => { console.log('I am the mocked private function') })
    ...
  })
})
matteo
  • 1,375
  • 1
  • 12
  • 24
  • I'm unclear how I can test this mocked function. How would I use the rewired function in the context of expect()? – Lounge9 Jan 08 '18 at 22:49
  • @matteo, I passed through exactly the same situation. I noted that mocked private functions aren't counted on coverage. In order to solve this coverage issue, I added the commend below on the line before the private function: `/* istanbul ignore next */` – Paulo Coghi Apr 13 '18 at 20:25
9

There is no way through the nature of JavaScript. The function is bound to the scope of the module, so there is no way to know that this function exists from the outside, so no way to access the function and in the end no way to mock it.

Maybe more important, you should not test on the internals of the object under test but only the public API. Cause that is everything that counts. No one cares how stuff is done internally as long as the public API stays stable.

Andreas Köberle
  • 88,409
  • 51
  • 246
  • 277
  • 11
    When I was using "tape" and "ava" I was able to mock private functions by using "babel-plugin-rewire". About testing private functions I disagree since it violates the principles of TDD. – matteo Apr 07 '17 at 09:03
  • 2
    The nature of javascript is "do whatever you want, whenever you want." There's nothing stopping you from inserting a like-named function with which to assert code flow. – Lucas Nov 19 '19 at 00:42
  • The nature of unit testing requires to spy and mock away stuff that is attached to the unit to be tested. – LSR Oct 13 '20 at 10:38
8

If you want to mock private function, try to use prototype. For example, you need to mock privateFunction of the next class:

export class Module {
    public publicFunction() {
        // do something
        this.privateFunction();
        // do something
    }

    private privateFunction() {
        // do something
    }
}  

So you should use Module.prototype in jest.spyOn function.

import { Module } from "./my-module";

describe('MyModule', () => {
it('test public function', () => {
    // Arrange
    const module = new Module()
    const myPrivateFunc = jest.spyOn(Module.prototype as any, 'privateFunction');
    myPrivateFunc.mockImplementation(() => {});

    // Act
    module.publicFunction();

    // Assert
    expect(myPrivateFunc).toHaveBeenCalled();
  });
});
  • the accepted answer here (https://stackoverflow.com/questions/56044471/testing-private-functions-in-typescript-with-jest) helped me along this comment. Thanks ! – Sufiane Dec 23 '20 at 14:53
  • 1
    Are there any problems/drawbacks with this solution? It works great for me but wondering if there's a catch... – Richard Apr 08 '21 at 11:27