【问题标题】:Mocking `document` in jest开玩笑地模拟“文档”
【发布时间】:2023-03-08 22:51:02
【问题描述】:

我正在开玩笑地尝试为我的 Web 组件项目编写测试。我已经使用带有 es2015 预设的 babel。加载 js 文件时遇到问题。我遵循了一段代码,其中document 对象有一个currentScript 对象。但在测试环境中是null。所以我想嘲笑同样的事情。但是jest.fn() 并没有真正的帮助。我该如何处理这个问题?

玩笑失败的一段代码。

var currentScriptElement = document._currentScript || document.currentScript;
var importDoc = currentScriptElement.ownerDocument;

我写的测试用例。 component.test.js

import * as Component from './sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

以下是jest抛出的错误

Test suite failed to run

    TypeError: Cannot read property 'ownerDocument' of null

      at src/components/sample-component/sample-component.js:4:39

更新: 根据 Andreas Köberle 的建议,我添加了一些全局变量并尝试模拟如下

__DEV__.document.currentScript = document._currentScript = {
  ownerDocument: ''
};
__DEV__.window = {
  document: __DEV__.document
}
__DEV__.document.registerElement = jest.fn();

import * as Component from './arc-sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

但没有运气

更新:我试过上面没有__dev__的代码。也可以通过将文档设置为全局。

【问题讨论】:

  • 您是否尝试过使用global.document
  • 是的..我试过了..没有运气..
  • 其实这里的问题是,jsdom 非常简单并且没有webcomponents API。无论如何,根据我的回答暂时解决了它。

标签: jestjs jsdom babel-jest


【解决方案1】:

与其他人所说的类似,但不要尝试自己模拟 DOM,只需使用 JSDOM:

// __mocks__/client.js

import { JSDOM } from "jsdom"
const dom = new JSDOM()
global.document = dom.window.document
global.window = dom.window

然后在你的笑话配置中:

    "setupFiles": [
      "./__mocks__/client.js"
    ],

【讨论】:

  • TypeError: _jsdom.JSDOM is not a constructor
  • 他们一定在一年前就改变了他们的 API?
  • 看起来new JSDOM() 仍然正确 - 也许您还有其他配置不正确,例如 babel? github.com/jsdom/jsdom
  • 他们不会将变更日志与发布的版本一起发布,这很难告诉github.com/jsdom/jsdom/releases
  • 是的,你需要 babel-jest 和 @babel/plugin-transform-modules-commonjs。它仍然适用于 Jest 24(我使用 ts-jest)并且非常适合模拟其他全局变量,如 window.navigator 或 navigator :-)
【解决方案2】:

我开玩笑地使用setUpFiles 属性解决了这个问题。这将在 jsdom 之后和每个测试之前执行,这对我来说是完美的。

在 Jest 配置中设置 setupFiles,例如:

"setupFiles": ["<rootDir>/browserMock.js"]


// browserMock.js
Object.defineProperty(document, 'currentScript', {
  value: document.createElement('script'),
});

理想的情况是加载 webcomponents.js 来填充 jsdom。

【讨论】:

    【解决方案3】:

    我一直在为我正在进行的项目模拟文档而苦苦挣扎。我在 React 组件中调用 document.querySelector() 并需要确保它正常工作。最终这对我有用:

    it('should test something', () => {
      const spyFunc = jest.fn();
      Object.defineProperty(global.document, 'querySelector', { value: spyFunc });
      <run some test>
      expect(spyFunc).toHaveBeenCalled()
    });
    

    【讨论】:

      【解决方案4】:

      如果您像我一样希望将文档模拟为未定义(例如用于服务器端/客户端测试),我可以在我的测试套件中使用 object.defineProperty 而无需使用 setupFiles

      例子:

      beforeAll(() => {
        Object.defineProperty(global, 'document', {});
      })
      

      【讨论】:

      • 我试图通过在对象中提供 document.referrerdocument.URL 来模拟它们......没有运气。这些属性在测试运行程序中仍然显示为它们通常的值。
      • @theUtherSide 我注意到此解决方案在某些版本的 Node 中有效,而在其他版本中无效。也许安装 NVM 并玩不同的版本
      • 感谢您的提示!我将使用 Node 版本并发布一些发现!我真的比使用 Jest 配置更喜欢这种策略。我在一个大团队工作,最好让我们的单元测试保持隔离,当然还有原子测试!
      • Confirming -- 我尝试过的应用程序使用的是 Node v8.11.1 和 Jest 21.2.1
      • TypeError: Cannot redefine property: document => `在 JSDOMEnvironment.teardown (node_modules/jest-environment-jsdom/build/index.js:192:14)`
      【解决方案5】:

      如果您需要为属性定义测试值,有一种更精细的方法。每个属性都需要单独定义,属性writeable也是需要的:

      Object.defineProperty(window.document, 'URL', {
        writable: true,
        value: 'someurl'
      });
      

      见:https://github.com/facebook/jest/issues/890

      这适用于我使用 Jest 21.2.1 和 Node v8.11.1

      【讨论】:

      • 谢谢。所以。很多。
      【解决方案6】:

      这是我的项目中名为 super-project 在文件夹 super-project 中的结构:


      • 超级项目
        • 配置
          • __mocks__
            • dom.js
          • user.js
        • 测试
          • user.test.js
        • jest.config.js
        • package.json

      您需要设置 Jest 以在测试中使用模拟:

      dom.js

      import { JSDOM } from "jsdom"
      const dom = new JSDOM()
      global.document = dom.window.document
      global.window = dom.window
      

      user.js

      export function create() {
        return document.createElement('table');  
      }
      

      user.test.js

      import { create } from "../src/user";
      
      test('create table', () => {
        expect(create().outerHTML).toBe('<table></table>');
      });
      

      jest.config.js

      module.exports = {
        setupFiles: ["./config/__mocks__/dom.js"],
      };
      

      参考资料:

      您需要手动创建模拟:
      https://jestjs.io/docs/en/manual-mocks.html

      操作 DOM 对象:
      https://jestjs.io/docs/en/tutorial-jquery

      笑话配置:
      https://jestjs.io/docs/en/configuration

      【讨论】:

        【解决方案7】:

        我可以使用 nodejs 上的 global 范围模块来解决同样的问题,使用文档模拟设置文档,在我的例子中,getElementsByClassName

        // My simple mock file
        export default {
            getElementsByClassName: () => {
                return [{
                    className: 'welcome'
                }]
            }
        };
        
        // Your test file
        import document from './name.component.mock.js';
        global.document = {
            getElementsByClassName: document.getElementsByClassName
        };
        

        【讨论】:

        • 是的……确实如此,但它的用例非常简单……在 webcomponents.js 中,我们有大量的 API 互连。嘲笑他们是一项艰巨的任务......
        • 但在你的情况下,你需要模拟什么?
        【解决方案8】:

        我找到了另一个解决方案。假设在您的组件内部,您希望通过 className (document.getElementsByClassName) 获取对 DOM 中元素的引用。您可以执行以下操作:

        let wrapper
        beforeEach(() => {
            wrapper = mount(<YourComponent/>)
            jest.spyOn(document, 'getElementsByClassName').mockImplementation(() => 
                [wrapper.find('.some-class').getDOMNode()]
            )
        })
        

        这样,您将手动设置 getElementsByClassName 的返回值等于 .some-class 的引用。可能需要通过调用 wrapper.setProps({}) 重新渲染组件。

        希望这对你们中的一些人有所帮助!

        【讨论】:

          【解决方案9】:

          希望对你有帮助

          const wrapper = document.createElement('div');
          const render = shallow(<MockComponent{...props} />);
          document.getElementById = jest.fn((id) => {
                wrapper.innerHTML = render.find(`#${id}`).html();
                return wrapper;
              });
          

          【讨论】:

          • 此答案假定组件是 React 组件。我没有看到任何表明这一点的东西。在我看来,它就像一个普通的 javascript Web 组件。
          猜你喜欢
          • 1970-01-01
          • 2017-07-01
          • 1970-01-01
          • 2020-03-05
          • 1970-01-01
          • 2019-03-23
          • 2020-11-18
          • 1970-01-01
          相关资源
          最近更新 更多