HomeAbout Me

React Testing 6: mocking Browser APIs and modules

By Daniel Nguyen
Published in React JS
September 07, 2025
3 min read
React Testing 6: mocking Browser APIs and modules

mocking Browser APIs and modules

Background

Mocking HTTP requests is one thing, but sometimes you have entire Browser APIs or modules that you need to mock. Every time you create a fake version of what your code actually uses, you’re “poking a hole in reality” and you lose some confidence as a result (which is why E2E tests are critical). Remember, we’re doing it and recognizing that we’re trading confidence for some practicality or convenience in our testing. (Read more about this in my blog post: The Merits of Mocking).

To learn more about what “mocking” even is, take a look at my blog post But really, what is a JavaScript mock?

Mocking Browser APIs

I need to tell you a little secret and I want you to promise me to not be mad…

Our tests aren’t running in the browser 😱😱😱😱😱

It’s true. They’re running in a simulated browser environment in Node. This is done thanks to a module called jsdom. It does its best to simulate the browser and implement standards. But there are some things it’s simply not capable of simulating today. One example is window resize and media queries. In my Advanced React Hooks workshop, I teach something using a custom useMedia hook and to test it, I have to mock out the browser window.resizeTo method and polyfill window.matchMedia. Here’s how I go about doing that:

import matchMediaPolyfill from 'mq-polyfill'
beforeAll(() => {
matchMediaPolyfill(window)
window.resizeTo = function resizeTo(width, height) {
Object.assign(this, {
innerWidth: width,
innerHeight: height,
outerWidth: width,
outerHeight: height,
}).dispatchEvent(new this.Event('resize'))
}
})

This allows me to continue to test with Jest (in node) while not actually running in a browser.

So why do we go through all the trouble? Because the tools we currently have for testing are WAY faster and WAY more capable when run in node. Most of the time, you can mock browser APIs for your tests without losing too much confidence. However, if you are testing something that really relies on browser APIs or layout (like drag-and-drop) then you may be better served by writing those tests in a real browser (using a tool like Cypress).

Mocking Modules

Sometimes, a module is doing something you don’t want to actually do in tests. Jest makes it relatively simple to mock a module:

// math.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
// __tests__/some-test.js
import {add, subtract} from '../math'
jest.mock('../math')
// now all the function exports from the "math.js" module are jest mock functions
// so we can call .mockImplementation(...) on them
// and make assertions like .toHaveBeenCalledTimes(...)

Additionally, if you’d like to mock only parts of a module, you can provide your own “mock module getter” function:

jest.mock('../math', () => {
const actualMath = jest.requireActual('../math')
return {
...actualMath,
subtract: jest.fn(),
}
})
// now the `add` export is the normal function,
// but the `subtract` export is a mock function.

To learn a bit about how this works, take a look at my repo how-jest-mocking-works. It’s pretty fascinating.

There’s a lot more to learn about the things you can do with Jest’s module mocking capabilities. You can also read the docs about this here:

📜 Manual Mocks

Exercise

We’ve got a Location component that will request the user’s location and then display the latitude and longitude values on screen. And yup, you guessed it, window.navigator.geolocation.getCurrentPosition is not supported by jsdom, so we need to mock it out. We’ll mock it with a jest mock function so we can call mockImplementation and mock what that function does for a particular test.

We’ll also bump into one of the few situations you need to use act directly. Learn more.

Extra Credit

1. 💯 mock the module

Sometimes, the module is interacting with browser APIs that are just too hard to mock (like canvas) or you’re comfortable relying on the module’s own test suite to give you confidence that so long as you use the module properly everything should work.

In that case, it’s reasonable to mock the module directly. So for this extra credit, try to mock the module rather than the browser API it’s using.

💰 tip, you’re mocking a hook. Your mock implementation can also be a hook (so you can use React.useState!).

2. 💯 test the unhappy path

NOTE: A recording of me doing this extra credit is not on EpicReact.Dev yet, but feel free to give it a try anyway!

Add a test for what happens in the event of an error. You can try it with the module mocking approach, but in my solution, I go back to the function mocking version.

🦉 Elaboration and Feedback

After the instruction, if you want to remember what you’ve just learned, then fill out the elaboration and feedback form:

https://ws.kcd.im/?ws=Testing%20React%20Applications%20%F0%9F%A7%90&e=06%3A%20mocking%20Browser%20APIs%20and%20modules&em=

Solution

import React from 'react'
import {render, screen, act} from '@testing-library/react'
import Location from '../../examples/location'
beforeAll(() => {
window.navigator.geolocation = {
getCurrentPosition: jest.fn(),
}
})
function deferred() {
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
return {promise, resolve, reject}
}
test('displays the users current location', async () => {
const fakePosition = {
coords: {
latitude: 35,
longitude: 139,
},
}
const {promise, resolve} = deferred()
window.navigator.geolocation.getCurrentPosition.mockImplementation(
callback => {
promise.then(() => callback(fakePosition))
},
)
render(<Location />)
expect(screen.getByLabelText(/loading/i)).toBeInTheDocument()
await act(async () => {
resolve()
await promise
})
expect(screen.queryByLabelText(/loading/i)).not.toBeInTheDocument()
expect(screen.getByText(/latitude/i)).toHaveTextContent(
`Latitude: ${fakePosition.coords.latitude}`,
)
expect(screen.getByText(/longitude/i)).toHaveTextContent(
`Longitude: ${fakePosition.coords.longitude}`,
)
})
test('displays error message when geolocation is not supported', async () => {
const fakeError = new Error(
'Geolocation is not supported or permission denied',
)
const {promise, reject} = deferred()
window.navigator.geolocation.getCurrentPosition.mockImplementation(
(successCallback, errorCallback) => {
promise.catch(() => errorCallback(fakeError))
},
)
render(<Location />)
expect(screen.getByLabelText(/loading/i)).toBeInTheDocument()
await act(async () => {
reject()
})
expect(screen.queryByLabelText(/loading/i)).not.toBeInTheDocument()
expect(screen.getByRole('alert')).toHaveTextContent(fakeError.message)
})

Tags

#React

Share

Previous Article
React Testing 5: mocking HTTP requests

Table Of Contents

1
mocking Browser APIs and modules
2
Solution

Related Posts

React Testing 8: Testing custom hook
September 09, 2025
1 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media