typescriptjestjsjsdomjest-dom

How to mock navigator.clipboard.writeText() in Jest?


I have tried the following 4 options after looking at Jest issues and SO answers, but I am either getting TypeScript errors or runtime errors. I would really like to get option 1 (spyOn) working.

// ------ option 1 -----
// Gives this runtime error: "Cannot spyOn on a primitive value; undefined given"
const writeText = jest.spyOn(navigator.clipboard, 'writeText');

// ------ option 2 -----
Object.defineProperty(navigator, 'clipboard', {
    writeText: jest.fn(),
});

// ------ option 3 -----
// This is from SO answer but gives a TypeScript error
window.__defineGetter__('navigator', function() {
    return {
        clipboard: {
            writeText: jest.fn(x => x)
        }
    }
})

// ------ option 4 -----
const mockClipboard = {
    writeText: jest.fn()
};
global.navigator.clipboard = mockClipboard;

Solution

  • Jest tests are running in JSdom environment and not all of the properties are defined, but so you should define the function before spying on it.

    Here is an example:

    const writeText = jest.fn()
    
    Object.assign(navigator, {
      clipboard: {
        writeText,
      },
    });
    
    describe("Clipboard", () => {
      describe("writeText", () => {
        beforeAll(() => {
          navigator.clipboard.writeText.mockResolvedValue(undefined)
          // or if needed
          // navigator.clipboard.writeText.mockRejectedValue(new Error()) 
          yourImplementationThatWouldInvokeClipboardWriteText();
        });
        it("should call clipboard.writeText", () => {
          expect(navigator.clipboard.writeText).toHaveBeenCalledWith("zxc");
        });
      });
    });
    

    Edit: you can also use Object.defineProperty, but it accepts descriptors object as third parameter

    Object.defineProperty(navigator, "clipboard", {
      value: {
        writeText: async () => {},
      },
    });