7

I have a test that needs to use navigator.mediaDevices but I cannot get any mocks to work correctly.

I am using create-react-app.

Here is my test:

import getConnectedDevices from "./getConnectedDevices";

describe("getConnectedDevices", () => {
  it("Should work", () => {
    console.log(navigator);              // Navigator {}
    console.log(navigator.mediaDevices); // undefined
  });
});

I have tried adding a mock as it states here in the jest documentation

// tests/navigator.mock
Object.defineProperty(window, "navigator", {
  writable: true,
  value: {
    mediaDevices: {
      enumerateDevices: jest.fn(),
    },
  },
});

import "../../tests/navigator.mock"; // <- Mock added

import getConnectedDevices from "./getConnectedDevices";

describe("getConnectedDevices", () => {
  it("Should work", () => {
    console.log(navigator);              // Navigator {}
    console.log(navigator.mediaDevices); // undefined
  });
});

I have also tried Initializing the test environment according to the create-react-app docs.

// src/setupTests.ts

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";

const navigatorMock = {
  mediaDevices: {
    enumerateDevices: jest.fn(),
  },
};

global.navigator = navigatorMock;

What am I doing wrong?

Karl Taylor
  • 4,839
  • 3
  • 34
  • 62

3 Answers3

11

I did it in typescript:

const mockGetUserMedia = jest.fn(async () => {
    return new Promise<void>(resolve => {
        resolve()
    })
})

Object.defineProperty(global.navigator, 'mediaDevices', {
    value: {
        getUserMedia: mockGetUserMedia,
    },
})
  • Thanks! That should be the accepted answer. – Fl4v Aug 19 '21 at 08:58
  • Note that with ES6 you can remove the return statement and the curly brackets to simplify your function: const mockGetUserMedia = jest.fn( async () => new Promise(resolve => { resolve(); }) ); – Fl4v Aug 19 '21 at 10:21
  • 2
    I think this answer needs more clarifying info. Where exactly is this called in comparison to the tests and are there any libraries needed outside of jest? I also tried implementing this code and it does not add 'mediaDevices' to the navigator property for me. – Logan Cundiff Dec 02 '21 at 18:45
  • 1
    You can simplify to `const mockGetUserMedia = jest.fn(async () => {});` or `jest.fn(Promise.resolve);` – skot Dec 07 '21 at 05:52
  • How to mock `getVideoTracks` I am getting TypeError: Cannot read properties of undefined (reading 'getVideoTracks') – Javascript Coder Apr 04 '22 at 09:35
  • This solution doesn't work. Still getting an error. – Pikachu Sep 08 '22 at 11:20
  • This answer did not work for me. However, [this answer worked perfectly](https://stackoverflow.com/questions/65112057/jest-spyon-navigator-mediadevices) – Ted Stresen-Reuter Sep 28 '22 at 09:28
1

It appears that because there already is a navigator object, it's not possible to re-assign it.

Thanks to this stackoverflow answer, you mock the object within the navigator and assign it to the existing navigator object.

// src/setupTests.ts

const mediaDevicesMock = {
  enumerateDevices: jest.fn(),
};

global.navigator.mediaDevices = mediaDevicesMock; // here

Note: This will throw a TSC error Cannot assign to 'mediaDevices' because it is a read-only property.ts(2540). Still trying to figure that one out.

Karl Taylor
  • 4,839
  • 3
  • 34
  • 62
  • 2
    You can use `Object.defineProperty()`, and don't forget to add `{ writable: true }` if you're going to overwrite it later in some test. – futuredayv May 07 '21 at 01:26
0

This worked for me:

const mediaDevicesMock = jest.fn(async () => {
  return new Promise<void>((resolve) => {
    resolve();
}); 
});

beforeEach(() => {
 Object.defineProperty(global.navigator, 'mediaDevices', {
  value: {
    enumerateDevices: mediaDevicesMock,
  },
});
})
niio
  • 326
  • 3
  • 15