53

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?

Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
matteo
  • 1,635
  • 1
  • 15
  • 26
  • 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

6 Answers6

38

If you want to mock a private function, try to use the prototype.

For example, you need to mock privateFunction of the following class:

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

    private privateFunction() {
        // do something
    }
}  

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

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

describe('MyModule', () => {
  it('tests 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();
  });
});
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Mikhail Savich
  • 481
  • 4
  • 3
  • 1
    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
  • Thanks for the pointer. Works properly. what if the private function and the public one are in a hook instead of a class ? – exaucae Jul 07 '21 at 13:41
  • If I don't (auto) mock the module it does not work with nested objects, and I usually get `TypeError: Cannot read property '(..).' of undefined` – Christian H Aug 04 '21 at 17:01
  • I appreciate the answer, but it doesn't seem to match the question. OP asked about mocking a private function rather than the private method of a class. Or maybe the answer indicates the module should be recreated as a class? – Erik Hermansen May 14 '23 at 21:44
26

I found 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');
    })
    ...
  })
})
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
matteo
  • 1,635
  • 1
  • 15
  • 26
  • 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
  • I keep trying this, but keep getting __Rewire__ is not a function. Is there any additional setup that you are doing? – Godrules500 Oct 17 '22 at 15:22
  • 1
    @Lounge9 to use the rewired function in the context of expect() you can do `let privateFunctionMock = jest.fn( () => { console.log('I am the mocked private function'); });`. Then, you do `moduleToTest.__Rewire__('privateFunction', privateFunctionMock);`. So you can call the `module.publicFunction();`. Now, you can assert: `expect(privateFunctionMock).toHaveBeenCalled();` – Ocimar Jul 07 '23 at 14:11
8

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
  • 106,652
  • 57
  • 273
  • 297
  • 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
  • 4
    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
  • 2
    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
  • Does it have to violate TDD? What about writing a section of test cases that _indirectly_ test all paths in a private function, by calling its public parent? – Ian Dunn Oct 10 '21 at 20:49
  • Re "you should not test on the internals of the object under test". Even following this pattern, there are situations that come up where it's useful to mock internals. – Erik Hermansen May 14 '23 at 21:48
3

Another option would be an explicit cast:

const spy = jest.spyOn((someInstance as unknown) as { privateMethod: SomeClass['privateMethod'] }, 'privateMethod');

It's a bit lengthy, but it has the advantage of keeping the typing of the private method for subsequent checks like spy.hasBeenCalledWith(...).

Rene Hamburger
  • 2,003
  • 16
  • 17
2

If you are calling a function (funA) inside another function (funB) but you want your tests to use a mocked version of funA, refactor funA to use a class for its implementation, then mock a method on the class' prototype.

Suppose you have this:

async function goodsDetails({ code }) {
   return goodsNameFor(code);
}
async function goodsNameFor(code) { 
  // ... perform long running operation
  return 'xyz'
}

If you want to mock goodsNameFor so that when you're testing goodsDetails function you use the goodsNameFor, then change the implementation of goodsNameFor to be like this:

class GoodsNameCommand {
  constructor({ code }) {
    this.code = code;
  }
  async executeAsync() {
       // ... perform long running operation
       return 'xyz'
  }
}
async function goodsNameFor(code) { 
  // ... perform long running operation
  return new GoodsNameCommand().executeAsync();
}

Then at the top of your test class have this:

requiredJest.spyOn(GoodsNameCommand.prototype, 'executeAsync')
.mockResolvedValue('Webstorm');
Gilbert
  • 2,699
  • 28
  • 29
0

The most simple way I found to spyOn private methods using ts-jest.

First assume following example class with private method doStuff you wanna spy on:

class Example{
 private doStuff(): void {
  ...doing stuff
 }
}

const example = new Example();

Then is how you write a spy

jest.spyOn(example as any, 'doStuff');
Pauli
  • 388
  • 4
  • 17