PGupta
PGupta

Reputation: 11

window.dispatchEvent from test code does not trigger window.addEventListener

I have below listener added for which I am trying to write test using Jest. However, it seems as though the event I'm dispatching doesn't reach my code.

window.addEventListener('message', (event) => {
    if (event.data.type === 'abc') {
      console.log(event.data.payload);
    }
});

I have tried below 2 approaches and both of them don't seem to work. I'm unable to verify the call using the spy object I'm creating. Please refer to the code below:

  1.  const listenerSpy = jest.spyOn(window, 'addEventListener');
    
     const data = {
       type: 'abc',
       payload: '',
     };
    
     const messageEvent = new MessageEvent('message', {data});
    
     window.dispatchEvent(messageEvent);
    
     expect(listenerSpy).toHaveBeenCalledTimes(1);
    
    
  2. const listenerSpy = jest.spyOn(window, 'addEventListener');
    
    const data = {
      type: 'abc',
      payload: '',
    };
    
    window.postMessage(data, '*');
    
    expect(listenerSpy).toHaveBeenCalledTimes(1);
    
    

For the 1st approach, have also tried using 'new Event('message')'.

With above 2 approaches, I get the error as below:

expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 0

  102 |     window.dispatchEvent(messageEvent);
  103 |
> 104 |     expect(listenerSpy).toHaveBeenCalledTimes(1);
      |                         ^

I have also tried to follow different websites including below: https://medium.com/@DavideRama/testing-global-event-listener-within-a-react-component-b9d661e59953 https://github.com/enzymejs/enzyme/issues/426

But no luck there as with typescript, I cannot follow the solution given. I have also tried to find answers on stackoverflow, but the none of solutions suggested seem to work for me.

I am new to react and got stuck with this. Any pointers on this would help.

Upvotes: 1

Views: 3144

Answers (2)

Lin Du
Lin Du

Reputation: 102317

jsdom fire the message event inside a setTimeout, see this

setTimeout(() => {
  fireAnEvent("message", this, MessageEvent, { data: message });
}, 0);

For more info, see issue

So, it's asynchronous and you need to wait for the macro task scheduled by setTimeout to be finished before the test case ends.

index.ts:

export function main() {
  window.addEventListener('message', (event) => {
    if (event.data.type === 'abc') {
      console.log(event.data.payload);
    }
  });
}

index.test.ts:

import { main } from './';

function flushMessageQueue(ms = 10) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

describe('71912032', () => {
  test('should pass', async () => {
    const logSpy = jest.spyOn(console, 'log');
    main();
    const data = { type: 'abc', payload: 'xyz' };

    window.postMessage(data, '*');

    await flushMessageQueue();
    expect(logSpy).toBeCalledWith('xyz');
  });
});

Test result:

 PASS  stackoverflow/71912032/index.test.ts
  71912032
    ✓ should pass (41 ms)

  console.log
    xyz

      at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.01 s, estimated 13 s

Also, take a look at this question React Jest: trigger 'addEventListener' 'message' from tests

Upvotes: 1

lynx
lynx

Reputation: 23

Have you tried taking a look at this discussion? It seems to have a similar requirement. Dispatch a Custom Event and test if it was correctly triggered (React TypeScript, Jest)

Upvotes: 0

Related Questions