如何在 Jest 中模拟 navigator.clipboard.writeText()?

Posted

技术标签:

【中文标题】如何在 Jest 中模拟 navigator.clipboard.writeText()?【英文标题】:How to mock navigator.clipboard.writeText() in Jest? 【发布时间】: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;

【问题讨论】:

【参考方案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 我没有发布单独的答案,因为您的答案总体上对我有用,这个小调整可能在您的答案之上的某些情况下有用,但感谢您的提醒!

以上是关于如何在 Jest 中模拟 navigator.clipboard.writeText()?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Jest 中模拟 .then() 和 .catch()? [关闭]

如何在 Jest 中模拟 navigator.clipboard.writeText()?

如何在 Jest 中模拟此方法链?

如何在 Jest 的模拟依赖项中更改方法的返回值?

如何在 JEST 中使用配置模拟 API 调用?

在 Jest 中,如何模拟具有依赖项的 TypeScript 类?