How to Mock Howler.js in Jest
Testing audio functionality in web applications can be challenging
because JSDOM, the default environment for Jest, does not support HTML5
Audio or the Web Audio API. This article provides a straightforward,
step-by-step guide on how to mock the popular audio library
howler.js in Jest, allowing you to run your unit tests
seamlessly without encountering environment-related audio errors.
Why You Need to Mock Howler.js
By default, howler.js attempts to access audio APIs like
window.AudioContext or HTML5 Audio elements.
Since these APIs do not exist in Node.js or JSDOM, calling
new Howl() in your test environment will cause your tests
to crash or throw errors. Mocking allows you to simulate the behavior of
the Howl class and assert that your application calls its
methods (like .play() or .stop())
correctly.
Step 1: Create the Howler Mock
You can mock howler.js globally by creating a manual
mock in your project’s __mocks__ directory, or inline
inside your test file. The global mock approach is recommended if you
use Howler across multiple test suites.
Create a file named howler.js inside a folder named
__mocks__ in your project’s root directory (or adjacent to
your node_modules folder if config allows):
// __mocks__/howler.js
const mockPlay = jest.fn().mockReturnValue(1001); // Returns a fake sound ID
const mockPause = jest.fn();
const mockStop = jest.fn();
const mockUnload = jest.fn();
const mockOn = jest.fn((event, callback) => {
// Automatically trigger 'load' event to simulate successful audio loading
if (event === 'load') {
callback();
}
});
const mockOnce = jest.fn();
const mockOff = jest.fn();
const mockVolume = jest.fn();
class MockHowl {
constructor(options) {
this.options = options;
}
play = mockPlay;
pause = mockPause;
stop = mockStop;
unload = mockUnload;
on = mockOn;
once = mockOnce;
off = mockOff;
volume = mockVolume;
}
module.exports = {
Howl: MockHowl,
Howler: {
volume: jest.fn(),
mute: jest.fn(),
unload: jest.fn(),
},
};Step 2: Use the Mock in Your Tests
With the mock created, Jest will automatically use it whenever a file
imports howler. In your test file, you must explicitly tell
Jest to mock the module, then you can write your assertions.
Here is an example of testing a React component or utility function that triggers audio playback:
import { Howl } from 'howler';
import { playSoundEffect } from './audioManager'; // Your custom audio trigger function
// Tell Jest to use the mocked version of howler
jest.mock('howler');
describe('Audio playback tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should trigger Howl.play when playSoundEffect is called', () => {
playSoundEffect('click.mp3');
// Access the mocked Howl class instance mock methods
const mockHowlInstance = Howl.mock.instances[0];
expect(Howl).toHaveBeenCalledWith(expect.objectContaining({
src: ['click.mp3']
}));
expect(mockHowlInstance.play).toHaveBeenCalledTimes(1);
});
});Inline Mock Alternative
If you only need to mock Howler in a single test file and do not want
to create a __mocks__ folder, you can define the mock
directly at the top of your test file:
jest.mock('howler', () => {
return {
Howl: jest.fn().mockImplementation(() => ({
play: jest.fn(),
pause: jest.fn(),
stop: jest.fn(),
unload: jest.fn(),
on: jest.fn(),
once: jest.fn(),
off: jest.fn(),
})),
Howler: {
volume: jest.fn(),
},
};
});