20

From Jest notes: Note: By default, jest.spyOn also calls the spied method.

In my Angular component.

ngAfterViewInit(): void {
  this.offsetPopoverPosition();
}

In my spec:

it('ngAfterViewInit() method should call offsetPopoverPosition() method', () => {
    const mockListener = jest.spyOn(cmp, 'offsetPopoverPosition');
    const spy = mockListener.mockImplementation(() => {
      console.log('in the mock');
    });

    cmp.ngAfterViewInit();
    expect(spy).toHaveBeenCalled();
  });

Simple. Yet the original function is still being called. I checked Jest 23.x docs: https://jestjs.io/docs/en/23.x/jest-object#jestspyonobject-methodname https://jestjs.io/docs/en/23.x/mock-function-api#mockfnmockimplementationfn

And few examples on the internets but I can't prevent jest from calling the original offsetPopoverPosition() method.

Any ideas?

I am cross linking to Jest github issue which is for some reason closed without resolving it.

Jest spyOn() calls the actual function instead of the mocked

4
  • Is offsetPopoverPosition bound to this in the component constructor? Commented Apr 25, 2019 at 17:41
  • No, it's called only in ngAfterViewInit
    – codeepic
    Commented Apr 25, 2019 at 18:31
  • ...ah, looks like cmp is an instance so it wouldn't make a difference anyway. From what I can see of your code it looks like it should work. (The reason why JonathanHolvey's code in the github link doesn't work is because processVisit is calling saveVisit directly so mocking the module export for saveVisit doesn't have any effect...that doesn't apply to your code since both of your functions are class methods from what I can see) Commented Apr 25, 2019 at 18:44
  • 1
    For me, it was an issue with modules/importing/exporting and scope/name resolution. This explanation here helped clarify things for me as a newbie to JavaScript: stackoverflow.com/a/45288360/7466271 Commented Jun 23, 2022 at 13:56

2 Answers 2

4

From my experience, the issue is you're resetting the original mock's intent. When you create a spy, it has its own implementation, by overriding it with mockImplementation, I've experienced the scenario you are describing - instead, try this:

cmp.offsetPopoverPosition = jest.fn().mockImplementation(() => {
      console.log('in the mock');
    });
const mockListener = jest.spyOn(cmp, 'offsetPopoverPosition');
// ... do work
expect(mockListener).toHaveBeenCalled[Times,With]()

also this assumes that cmp is an instance of the component and not just it's definition reference

edit: please note that mocking out a messaged function inside of the component you are testing is a misguided approach to unit testing. Instead of testing communication to the sameComponent.method - test any messaging that chained method uses outside of the component being tested - With the brief question content, please ignore the testing approach advice I've given if its reading tea leaves and not relevant to your unit test design(s)

1
  • If so, then what is the point of mockImplementation provided by the spyInstance object returned by spyOn? Also, in your example, you're NOT mocking the original offsetPopoverPosition()` in the original cmp. Commented Nov 17, 2021 at 4:58
0

As suggested in the GitHub issue, the only thing that ended up working for me was to call the target function from the import of the same file. Something like this in React.

// cmp.js
import * as thisModule from './cmp';

offsetPopoverPosition = () => {}

ngAfterViewInit = () => {
  thisModule.offsetPopoverPosition();
}

It's a weird issue...

The optimal solution does seem to just avoid this scenario by testing other items however (see this other answer).

Not the answer you're looking for? Browse other questions tagged or ask your own question.