【问题标题】:How to mock navigator.clipboard.writeText() in Jest?如何在 Jest 中模拟 navigator.clipboard.writeText()?
【发布时间】:2020-10-02 17:21:16
【问题描述】:

在查看 Jest 问题和 SO answers 后,我尝试了以下 4 个选项,但我遇到了 TypeScript 错误或运行时错误。我真的很想让选项 1 (spyOn) 工作。

// ------ 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;

【问题讨论】:

    标签: typescript jestjs jsdom jest-dom


    【解决方案1】:

    在我的环境中,testing-library 苗条和开玩笑的 jsdom,我没有设法模拟 global.navigator。有效的解决方案是在我的测试中模拟 window.navigator

    describe('my-test', () => {
    
      it("should copy to clipboard", () => {
        const { getByRole } = render(MyComponent);
    
        Object.assign(window.navigator, {
          clipboard: {
            writeText: jest.fn().mockImplementation(() => Promise.resolve()),
          },
        });
    
        const button = getByRole("button");
        fireEvent.click(button);
    
        expect(window.navigator.clipboard.writeText)
          .toHaveBeenCalledWith('the text that needs to be copied');
      });
    
    });
    

    【讨论】:

      【解决方案2】:

      我扩展了早期的解决方案,还为readText 提供了模拟剪贴板功能,因此可以测试剪贴板的内容。

      这是我的test.js文件的全部内容

      import copyStringToClipboard from 'functions/copy-string-to-clipboard.js';
      
      // ------- Mock -------
      //Solution for mocking clipboard so it can be tested credit: <link to this post>
      const originalClipboard = { ...global.navigator.clipboard };
      
      beforeEach(() => {
          let clipboardData = '' //initalizing clipboard data so it can be used in testing
          const mockClipboard = {
              writeText: jest.fn(
                  (data) => {clipboardData = data}
              ),
              readText: jest.fn(
                  () => {return clipboardData}  
              ),
          };
          global.navigator.clipboard = mockClipboard;
      
      });
      
      afterEach(() => {
          jest.resetAllMocks();
          global.navigator.clipboard = originalClipboard;
      });
      // --------------------
      
      
      it("copies a string to the clipboard", async () => {
          
          //arrange
          const string = 'test ?'
        
          //act
          copyStringToClipboard(string)
      
          //assert
          expect(navigator.clipboard.readText()).toBe(string)
          expect(navigator.clipboard.writeText).toBeCalledTimes(1);
          expect(navigator.clipboard.writeText).toHaveBeenCalledWith(string);
      });
      

      【讨论】:

        【解决方案3】:

        我也遇到过类似的情况,用下面的方法在导航器对象中模拟剪贴板:

          const originalClipboard = { ...global.navigator.clipboard };
          const mockData = {
             "name": "Test Name",
             "otherKey": "otherValue"
          }
        
          beforeEach(() => {
            const mockClipboard = {
              writeText: jest.fn(),
            };
            global.navigator.clipboard = mockClipboard;
        
          });
        
          afterEach(() => {
            jest.resetAllMocks();
            global.navigator.clipboard = originalClipboard;
          });
        
          test("copies data to the clipboard", () => {
            copyData(); //my method in the source code which uses the clipboard
            expect(navigator.clipboard.writeText).toBeCalledTimes(1);
            expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
              JSON.stringify(mockData)
            );
          });
        

        【讨论】:

        • 谢谢。这个解决方案很完美!
        • 谢谢。这个解决方案奏效了。 ??
        【解决方案4】:

        Jest 测试在 JSdom 环境中运行,并没有定义所有的属性,但是你应该在监视它之前定义函数。

        这是一个例子:

        Object.assign(navigator, {
          clipboard: {
            writeText: () => {},
          },
        });
        
        describe("Clipboard", () => {
          describe("writeText", () => {
            jest.spyOn(navigator.clipboard, "writeText");
            beforeAll(() => {
              yourImplementationThatWouldInvokeClipboardWriteText();
            });
            it("should call clipboard.writeText", () => {
              expect(navigator.clipboard.writeText).toHaveBeenCalledWith("zxc");
            });
          });
        });
        

        编辑:你也可以使用Object.defineProperty,但它接受描述符对象作为第三个参数

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

        【讨论】:

        • 谢谢@Teneff。这是一个很好的玩笑和 jsdom 教育!
        • 我不得不像这样调整Object.assign(navigator, { clipboard: { writeText: jest.fn().mockImplementation(() =&gt; Promise.resolve()), }, });
        • @gawkface 你可能想发布一个单独的答案是你认为它可以帮助某人
        • @Teneff 我没有发布单独的答案,因为您的答案总体上对我有用,这个小调整可能在您的答案之上的某些情况下有用,但感谢您的提醒!
        猜你喜欢
        • 2020-04-25
        • 2021-06-09
        • 2021-11-02
        • 2019-12-26
        • 2021-08-20
        • 2018-11-18
        • 1970-01-01
        • 1970-01-01
        • 2022-12-09
        相关资源
        最近更新 更多