Reputation: 687
I'm writing a test that needs to simulate and provide accessor facilities for validating my Audio implementation is correct. I successfully implemented my setup method like so:
import "regenerator-runtime/runtime";
import { AudioState } from '../src';
export class MockAudio extends Audio {
src: string;
state: AudioState;
duration: number;
playing: boolean;
constructor() {
super();
this.playing = false;
this.duration = NaN;
this.state = AudioState.STOPPED;
this.src = '';
}
fastSeek = (time: number): Promise<void> => {
this.currentTime = time;
return Promise.resolve();
}
play = (): Promise<void> => {
this.playing = true;
this.state = AudioState.PLAYING;
return super.play();
}
pause = (): void => {
this.playing = false;
this.state = AudioState.PAUSED;
return super.pause();
}
}
HTMLMediaElement.prototype.pause = () => Promise.resolve();
HTMLMediaElement.prototype.play = () => Promise.resolve();
window.Audio = MockAudio;
Object.defineProperty(window, 'MediaSource', {
writable: true,
value: jest.fn().mockImplementation((params) => ({
// MediaSource implementation goes here
addEventListener: jest.fn(),
})),
});
As you can see I'm setting the methods up so that they can effect the object properties in a way that simulates the Audio API. I'm wondering how I can do this properly with mockImplementation and defineProperty?
Upvotes: 4
Views: 5272
Reputation: 33
You are correct about play returning Promise. However, i still needed mocks in order to see if method was called. Crazy thing, this doesn't work:
export const mocks = {
Audio: {
pause: jest.fn(),
play: jest.fn(() => Promise.resolve()),
},
}
The code breaks saying that in file.play().catch(...)
returns: TypeError: Cannot read property 'catch' of undefined
Somehow returned promise gets lost and play method always returns undefined
. I tried all possible jest APIs and everything gives result. But this works play: () => Promise.resolve()
but then i cannot test if it was called or not...
Upvotes: 0
Reputation: 68
It seems Audio.play()
returns a Promise: HTMLMediaElement.play(): Promise<void>
see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play
This worked best for me using Typescript
global.Audio = jest.fn().mockImplementation(() => ({
pause: jest.fn(),
play: jest.fn(() => Promise.resolve()),
}));
Upvotes: 0
Reputation: 116
You can solve this problem (or mocking any other inbuild API classes) using what is described here: https://jestjs.io/docs/es6-class-mocks#manual-mock
What I like to do is to first define my mocks for later use in a test-utils.js
like this:
export const mocks = {
Audio: {
pause: jest.fn(),
play: jest.fn(),
},
}
Now I mock the actual class in my jest.setup.js
(as defined in the jest config):
import { mocks } from "./test-utils"
// Audio mock
global.Audio = jest.fn().mockImplementation(() => ({
pause: mocks.Audio.pause,
play: mocks.Audio.play,
}))
and finally I can expect against the mocks in my test:
import { mocks } from "./test-utils"
describe("Something", () => {
it("should work", () => {
// run your test here
expect(mocks.Audio.pause).toHaveBeenCalled()
})
})
of course remember to clean up the mocks as needed.
And the relevant part of the jest config in my package.json
to trigger the setup file:
"jest": {
...,
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.js"
],
...
},
Upvotes: 4