简体   繁体   English

无法使用 Jest 和 Testing-Library 测试 React 组件渲染,因为 Jest 没有可用的文档

[英]Trouble testing React component render with Jest and Testing-Library because there's no Document available to Jest

Context/Setup:上下文/设置:

I'm trying to use Jest and React-Testing-Library to test the render of a React component <Main/>, but when I run the test, the client that handles fetch throws an error because it's using document.querySelector() - but when Jest runs there's no document because there's no browser rendered.我正在尝试使用 Jest 和 React-Testing-Library 来测试 React 组件 <Main/> 的呈现,但是当我运行测试时,处理fetchclient会抛出错误,因为它正在使用document.querySelector() -但是当 Jest 运行时没有document ,因为没有呈现浏览器。

My Goal Here: Get Jest and RTL set up so we can start writing tests for all the components.我的目标是:设置 Jest 和 RTL,以便我们可以开始为所有组件编写测试。 I would like to start with verifying that I can render <Main/> without errors.我想首先验证我可以无错误地渲染<Main/>

Here's Client.js这是Client.js

class Client {
  constructor() {
    console.log("Client initializing")
    console.log(document.querySelector('meta[name="csrf-token"]'))
    console.log(document)

    this.token = document.querySelector('meta[name="csrf-token"]').content;
    
  }

  getData(path) {
    return (
      fetch(`${window.location.origin}${path}`, {
        headers: { "X-CSRF-Token": this.token }
      })
    )
  }

  submitData(path, method, body) {
    return (
      fetch(`${window.location.origin}${path}`, {
        method: method,
        headers: { "X-CSRF-Token": this.token },
        body: body
      })
    )
  }

  deleteData(path) {
    return (
      fetch(`${window.location.origin}${path}`, {
        method: "DELETE",
        headers: {
          "X-CSRF-Token": this.token,
          "Content-Type": "application/json"
        }
      })
    )
  }

}
export default Client;

Here's main.test.js :这是main.test.js

/**
 * @jest-environment jsdom
 */

import React from 'react';
import { render, screen } from '@testing-library/react';
// import userEvent from '@testing-library/user-event';
import Main from '../../app/javascript/components/Main';

test("renders without errors", ()=> {
    render(<Main/>);

});

I've also set up a setupTests.js file:我还设置了一个setupTests.js文件:


require("jest-fetch-mock").enableMocks();
import '@testing-library/jest-dom';

And called it here in package.json :并在package.json中调用它:

"jest": {
        "roots": [
            "test/javascript"
        ],
        "moduleNameMapper": {
            "\\.(svg|png)": "<rootDir>/__mocks__/svgrMock.js"
        },
        "automock": false,
        "setupFilesAfterEnv": [
            "./test/javascript/setupTests.js"
        ]
    },

I have also set testEnvironment: 'jsdom' in the jest.config.js file.我还在jest.config.js文件中设置了 testEnvironment testEnvironment: 'jsdom'

Current Problem:当前问题:

When I run yarn jest I get the following error: TypeError: Cannot read properties of null (reading 'content') which points to this.token = document.querySelector('meta[name="csrf-token"]').content;当我运行yarn jest时,出现以下错误: TypeError: Cannot read properties of null (reading 'content') which points to this.token = document.querySelector('meta[name="csrf-token"]').content; in Client.jsClient.js

This makes sense to me because it's looking for a DOM element, but Jest runs in Node (no browser render) so there's no DOM to be found.这对我来说很有意义,因为它正在寻找 DOM 元素,但是 Jest 在 Node 中运行(没有浏览器渲染)所以找不到 DOM。

I think I need to:我需要:

  1. Mock the document so the app can run without being rendered in the browser.模拟document ,以便应用程序可以在不在浏览器中呈现的情况下运行。 Not sure how to do this.不知道该怎么做。
  2. Then mock the fetch calls (maybe?) not sure how to do this either tbh.然后模拟提取调用(也许?)不知道该怎么做。

What I've Tried So Far:到目前为止我尝试过的:

1. I've tried various ways to mock the DOM elements globally (from setupTests.js ) including many permutations of something like this: 1. 我已经尝试了各种方法来全局模拟 DOM 元素(来自setupTests.js ),包括许多类似这样的排列:

import { TextDecoder, TextEncoder } from 'util'
global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder

//variables to mock a csrf token
const csrfToken = 'abcd1234';
const virtualDom = `
<!doctype html>
    <head>
        <meta name="csrf-token" content="${csrfToken}" />
    </head>
  <body>
    <form>
        <input type="hidden" name="csrf-token" value=${csrfToken}>
      </form>
  </body>
</html>
`;

const { JSDOM } = require("jsdom");
//mock a page passing virtualDom to JSDOM
const page = new JSDOM(virtualDom);

const { window } = page;

function copyProps(src, target) {
    const props = Object.getOwnPropertyNames(src)
      .filter(prop => typeof target[prop] === 'undefined')
      .map(prop => Object.getOwnPropertyDescriptor(src, prop));
    Object.defineProperties(target, props);
  }

global.window = window;
global.document = window.document;
global.navigator = {
  userAgent: 'node.js',
};
copyProps(window, global);

But global.window = window never seems to work because if I declare that and then immediately console.log(global.window, window) i get null and then a window.但是global.window = window似乎从来没有工作,因为如果我声明然后立即console.log(global.window, window)我得到 null 然后是 window。

2. I've tried temporarily downgrading React 18 to React 17 (based on some StackOverflow exchanges) - I know this isn't optimal, but it got me to the point where I would have to mock the fetch() calls at least. 2. 我已经尝试将 React 18 暂时降级到 React 17(基于一些 StackOverflow 交换)——我知道这不是最佳选择,但它让我到了至少必须模拟 fetch() 调用的地步。

I don't know how to do that properly either tbh, but I also know downgrading React is probably the wrong path here.我也不知道如何正确地做到这一点,但我也知道降级 React 可能是错误的路径。

Other potentially-important context:其他可能重要的上下文:

  • This React front-end is part of a Rails app (webpack).这个 React 前端是 Rails 应用程序 (webpack) 的一部分。
  • Using yarn使用yarn
  • I have a lot of control over how we achieve this, so I'm looking for the simplest/cleanest way to do this.我对我们如何实现这一点有很多控制权,所以我正在寻找最简单/最干净的方法来做到这一点。

Jest uses jsdom under the hood, so it's not true that "there is no DOM", however all the page is instantiated in the test - nothing comes from server(and jsdom neither supports navigation to request real page from the server and then continue testing). Jest 在后台使用jsdom ,所以“没有 DOM”是不正确的,但是所有页面都在测试中实例化了 - 没有来自服务器的东西(并且jsdom既不支持导航以从服务器请求真实页面然后继续测试).

Therefore we need to render element in the test:因此我们需要在测试中渲染元素:

render(<>
  <meta name="csrf-token" content="mocked-token" />
  <Main/>
</>);

Though I'm not sure why complete page replacement you tried in globalSetup did not work, maybe Jest does not allow to override JSDOM instance this way and binds earlier than globalSetup.js is ever run.虽然我不确定为什么您在 globalSetup 中尝试的完整页面替换不起作用,但也许 Jest 不允许以这种方式覆盖 JSDOM 实例并且在运行 globalSetup.js 之前绑定。

Turns out I had to mock all the data coming in to the components that render from Main.事实证明,我必须模拟所有进入从 Main 呈现的组件的数据。 (In addition to the CSRF token mocking) (除了 CSRF 令牌模拟)

Once I did that, everything else fell into place.一旦我这样做了,其他一切就都到位了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM