This guide walks through testing React components that rely on browser APIs like window, document, localStorage, matchMedia, or IntersectionObserver. You’ll learn how to mock these APIs in Jest and Testing Library so your tests run reliably in Node.js, which lacks a real browser environment.
What You’ll Need
- A React project with Jest and React Testing Library installed (Create React App includes these by default).
- Basic familiarity with writing Jest tests and using
render,screen, andfireEventfrom Testing Library. - Node.js 14 or newer.
Step-by-Step
- Identify which browser APIs your component uses. Search your component for direct references to
window,document,navigator,localStorage,sessionStorage,matchMedia,IntersectionObserver,ResizeObserver,requestAnimationFrame, orfetch. List them so you know what to mock. - Set up a test file. Create a file like
MyComponent.test.jsnext to your component. Import the component and Testing Library utilities at the top. - Mock a global API with
jest.spyOn. For simple methods likewindow.scrollTo, usejest.spyOn(window, 'scrollTo').mockImplementation(() => {})inside abeforeEachblock. This replaces the method with a no-op and restores it automatically after each test if you calljest.restoreAllMocks()inafterEach. - Mock properties with
Object.defineProperty. For read-only properties likewindow.innerWidthornavigator.clipboard, useObject.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: 1024 }). This lets you change the value per test. - Mock
localStorageandsessionStorage. Create a simple in-memory storage mock in abeforeEach:
const storageMock = () => {
let store = {};
return {
getItem: jest.fn((key) => store[key] ?? null),
setItem: jest.fn((key, value) => { store[key] = String(value); }),
removeItem: jest.fn((key) => { delete store[key]; }),
clear: jest.fn(() => { store = {}; }),
};
};
Object.defineProperty(window, 'localStorage', { value: storageMock() });
Object.defineProperty(window, 'sessionStorage', { value: storageMock() });
- Mock
matchMediafor responsive components. SincematchMediais not implemented in jsdom, define it as a function that returns an object withmatches,addListener,removeListener,addEventListener, andremoveEventListenermethods. Usejest.fn()for each method. - Mock
IntersectionObserverandResizeObserver. These are also missing in jsdom. Create a class mock with a constructor that captures the callback, and methodsobserve,unobserve, anddisconnectas jest.fn(). You can then trigger the callback manually in tests to simulate intersection changes. - Mock
fetchfor network calls. Usejest.spyOn(global, 'fetch').mockResolvedValue({ json: async () => ({ data: 'mocked' }) }). For more complex scenarios, considermsw(Mock Service Worker) but a simple mock is fine for most cases. - Write your test. Render the component, interact with it using
fireEventoruserEvent, and assert on the output. For example, if the component readslocalStorageon mount, set a value before rendering and check that the component displays it. - Clean up after each test. In
afterEach, calljest.clearAllMocks()andjest.restoreAllMocks(). Also reset any custom property values you set withObject.definePropertyto avoid leaking state between tests.
Common Mistakes
- Forgetting to restore mocks after tests, causing tests to interfere with each other.
- Mocking too broadly (e.g., mocking the entire
windowobject) instead of just the specific API your component uses. - Not setting
writable: trueandconfigurable: trueinObject.defineProperty, leading to errors when trying to reassign. - Assuming jsdom implements all browser APIs—it does not. Always check if the API exists in the test environment.
- Using
jest.fn()without a default implementation when the component expects a return value (e.g.,matchMediamust return an object).
Tips
- Create a
setupTests.jsfile (CRA uses this) to define global mocks for APIs used across many components, likematchMediaorIntersectionObserver. - Use
jest.spyOninstead of directly assigning towindow.method—it gives you automatic access to the original implementation and easier restoration. - For
matchMedia, make thematchesproperty configurable per test by storing it in a variable that you can change before rendering. - When testing
IntersectionObserver, expose the callback via a mock class and call it manually to simulate an element becoming visible. - Keep your mocks minimal—only mock what the component actually uses. Over-mocking can hide real integration issues.
Troubleshooting
Test fails with “TypeError: Cannot read property ‘addEventListener’ of undefined�. This usually means you forgot to mock matchMedia or IntersectionObserver. Add the mock in beforeEach.
Test fails with “ReferenceError: localStorage is not defined�. jsdom does provide localStorage, but if you’re using a custom environment or have cleared it, define it as shown in step 5.
Component renders but doesn’t update when you change a mock value. Ensure you’re re-rendering the component after changing the mock. Use rerender from Testing Library or unmount/remount.
Your mock implementation is called but the component still throws. Check that the mock returns the correct shape. For example, fetch must return a promise that resolves to an object with a json method.
Tests pass in isolation but fail when run together. This is a classic sign of mock leakage. Add jest.restoreAllMocks() and reset any Object.defineProperty values in afterEach.
Frequently Asked Questions
Do I need to mock window itself?
No, only the specific properties or methods your component uses. Mocking the whole window is unnecessary and can break other things.
Can I use real browser APIs in tests?
Not in jsdom, which is the default test environment for Create React App. You must mock them. If you need real browser behavior, use a tool like Cypress for end-to-end tests.
How do I mock window.location?
Use Object.defineProperty(window, 'location', { value: { href: 'https://example.com', ... }, writable: true }). Be careful to include all properties your component reads.
What about document.querySelector?
Testing Library encourages querying rendered DOM elements via screen, so you shouldn’t need to mock querySelector. If your component uses it directly, you can spy on it, but it’s better to refactor the component to use refs or Testing Library queries.
Is it okay to mock fetch globally?
Yes, but be aware that it will affect all components in that test file. If you need different responses for different tests, use mockResolvedValueOnce or change the implementation inside each test.
How do I test a component that uses requestAnimationFrame?
Mock it with jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => setTimeout(cb, 0)) to run the callback synchronously, or use jest.useFakeTimers() and advance timers manually.
Related
General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.
More in this bed
- Google Desktop Search Widget on Windows 11
- How to Build a Computer Stand (Saturday Workshop)
- Customize Touch Keyboard Themes in Windows 11
- Excel Pivot Table: Change Multiple Field Settings
- WhatsApp: How to Block Unknown Numbers
- How to Exit Vi in Linux: A Simple Guide