如何在Jest中Mock navigator.mediaDevices(Create React App环境)
Let's walk through why your current mocks aren't working and how to fix them properly.
Why Your Previous Approaches Failed
- Scope Mismatch: In Jest, browser globals like
navigatorlive on theglobalobject, not alwayswindow. Your mock targetingwindow.navigatormight not have been applied to the correct scope. - Read-Only Property: The default
navigatorobject in jsdom is often read-only. Directly assigningglobal.navigator = navigatorMockwon't work unless you explicitly mark it as writable. - Import Order: If your
getConnectedDevicesfunction accessesnavigator.mediaDeviceswhen it's imported (not just when called), your mock needs to be applied before importing the function.
Solution 1: Global Mock in setupTests.ts
This is the cleanest approach for all tests. Update your src/setupTests.ts to properly define the writable mock:
import "@testing-library/jest-dom"; // Mock navigator.mediaDevices globally Object.defineProperty(global.navigator, 'mediaDevices', { writable: true, configurable: true, value: { enumerateDevices: jest.fn().mockResolvedValue([ // Optional: Add mock device data for realistic testing { deviceId: 'camera-1', kind: 'videoinput', label: 'Built-in Camera', groupId: 'group-1' }, { deviceId: 'mic-1', kind: 'audioinput', label: 'Built-in Microphone', groupId: 'group-1' } ]), }, });
This ensures the mock is available before any test files or components are loaded.
Solution 2: Mock Directly in the Test File
If you need a test-specific mock, apply it before importing your function:
// First apply the mock Object.defineProperty(global.navigator, 'mediaDevices', { writable: true, value: { enumerateDevices: jest.fn().mockResolvedValue([]), }, }); // Then import the function under test import getConnectedDevices from "./getConnectedDevices"; describe("getConnectedDevices", () => { it("Should work with mocked mediaDevices", async () => { console.log(navigator.mediaDevices); // Will now be defined const devices = await getConnectedDevices(); expect(devices).toEqual([]); expect(navigator.mediaDevices.enumerateDevices).toHaveBeenCalled(); }); });
Solution 3: SpyOn for Targeted Method Mocking
If you only need to mock the enumerateDevices method (and keep the rest of mediaDevices intact):
import getConnectedDevices from "./getConnectedDevices"; describe("getConnectedDevices", () => { beforeEach(() => { // Ensure mediaDevices exists first if (!navigator.mediaDevices) { Object.defineProperty(navigator, 'mediaDevices', { writable: true, value: {}, }); } // Spy on and mock the method jest.spyOn(navigator.mediaDevices, 'enumerateDevices') .mockResolvedValue([/* your mock devices */]); }); afterEach(() => { // Clean up mocks to avoid test pollution jest.restoreAllMocks(); }); it("Should fetch connected devices correctly", async () => { const devices = await getConnectedDevices(); expect(navigator.mediaDevices.enumerateDevices).toHaveBeenCalledTimes(1); expect(devices).toHaveLength(2); // Match your mock data length }); });
Key Testing Tips
- Handle Async Code: Always use
async/awaitin your tests sinceenumerateDevicesreturns a promise. - Mock Realistic Data: Use device objects that match the structure your function expects (including
kind,deviceId, etc.). - Clean Up Mocks: Use
afterEachto restore mocks if usingspyOn, so tests don't interfere with each other.
内容的提问来源于stack exchange,提问作者Karl Taylor
相关产品推荐
相关产品推荐

