Reputation: 31
wanted to mock EventSource using jest, but kept throwing ReferenceError: EventSource is not defined
.
Please have a look at the code. Thanks a lot!
// eventSourceHandler.ts
export default new class A() {
listenEventSource(){
const eventSource = new EventSource(url);
eventSource.addEventListener("something", callSomething);
eventSource.onerror = function() {
console.error();
("Failed to listen EventSource");
};
}
}
Here is test code I want to mock
// eventSourceHandler.spec.ts
import A from "./eventSourceHandler"
describe("xyz",() =>{
it("eventSourceHandler called", ()=> {
const mEventSourceInstance = {
addEventListener: jest.fn(),
onerror: jest.fn(),
close: jest.fn(),
onmessage: jest.fn(),
onopen: jest.fn(),
url: "test-url",
readyState: 0,
withCredentials: false,
CLOSED: 2,
CONNECTING: 0,
OPEN: 1,
removeEventListener: jest.fn(),
dispatchEvent: jest.fn()
};
jest.mock("EventSource", () => {
return {
EventSource: jest.fn().mockImplementation(() => {
return {
// addEventListener: jest.fn(),
// onerror: jest.fn()
mEventSourceInstance
};
})
};
});
let a = new A()
a.listenEventSource();
// test validation ....
});
});
});
...
Kept getting ReferenceError: EventSource is not defined
whenever run test code.
NOTE: I've read almost most related posts from stackoverflow and tried to mock global.EventSource
but Typescript kept throwing error saying EventSource does not exist on type Global
.
Is there anyone who wants to share a better mocking strategy for this? That will be highly appreciated.
Thanks guyzz ...
Upvotes: 3
Views: 7028
Reputation: 1059
I was struggling with ReferenceError: EventSource is not defined
for a while.
But turns out official jest doc has pretty good explanation of what is up.
It's just that global objects are hoisted as soon as your import your piece of code that uses it and then can not be changed (kind of like a closure in my understanding).
So an easy work-around is to keep your definition of mocked global variables like EventSource
in a separate module and import it before any other module that uses globals.
For instantce I have a module mockEventSource.ts
with a following contents
Object.defineProperty(window, 'EventSource', {
writable: true,
value: jest.fn().mockImplementation(() => ({
close: jest.fn(() => {}),
addEventListener: jest.fn(
(_event: string, _callback: (_message: MessageEvent) => {}) => {},
),
})),
});
Then in my test file I do:
import './mockEventSource.ts';
import { myFunction } from './moduleToTest.ts';
And now when I call myFunction
it should have an access to mocked EventSource
Upvotes: 4
Reputation: 11492
As @amir-raminfar suggests, eventsourcemock
should do the trick. After npm install eventsourcemock
, I added the following to my jest setup tests file (I was using create-react-app, so it was just ./setupTests.js for me:
import EventSource from 'eventsourcemock';
Object.defineProperty(window, 'EventSource', {
value: EventSource,
});
Then, in the test, you would have something like
import {sources} from 'eventsourcemock';
const messageEvent = new MessageEvent('something', {
data: '1',
});
sources[url].emit(
messageEvent.type,
messageEvent
);
expect(... your expected behaviour here);
The sources[url]
will return a direct reference to the implementation the application code is using, so there's various things you can manipulate and inspect. In my tests, I couldn't get my onmessage
listener to fire with emit
, but if I called sources[url].onmessage
directly with my test payload, that worked well.
Upvotes: 1
Reputation: 660
Well, I see two alternatives you might use.
For the latter, you could end up with something like this:
utils.ts
export const buildEventSource = (url: string) => {
return new EventSource(url, {});
};
And then in your test class:
import * as utils from './utils';
const buildEventSourceSpy = jest.spyOn(utils, 'buildEventSource');
buildEventSourceSpy.mockReturnValue({
CLOSED: 0,
CONNECTING: 0,
OPEN: 0,
dispatchEvent(event: Event): boolean {
return false;
},
onerror: jest.fn(),
onmessage: jest.fn(),
onopen: jest.fn(),
readyState: 0,
url: '',
withCredentials: false,
addEventListener(
type: any,
listener: any,
options?: boolean | AddEventListenerOptions
): void {},
close(): void {},
removeEventListener(
type: any,
listener: any,
options?: boolean | EventListenerOptions
): void {}
});
I hope it helps
Upvotes: 4