19

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

skyboyer
  • 22,209
  • 7
  • 57
  • 64
codeepic
  • 3,723
  • 7
  • 36
  • 57
  • Is `offsetPopoverPosition` bound to `this` in the component constructor? – Brian Adams Apr 25 '19 at 17:41
  • No, it's called only in ngAfterViewInit – codeepic Apr 25 '19 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) – Brian Adams Apr 25 '19 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: https://stackoverflow.com/a/45288360/7466271 – Nolan Strait Jun 23 '22 at 13:56

1 Answers1

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)

Brandt Solovij
  • 2,124
  • 13
  • 24
  • 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`. – Nawaz Nov 17 '21 at 04:58