从 ReactDOM.render 切换到 createRoot 使简单的玩笑测试失败

问题描述 投票:0回答:2

我刚刚开始学习 Daniel Irvine 的《Mastering React Test-Driven Development》,我认为将示例转换为 React 18 应该不会太难。但是我在转换书中的第一个测试时遇到了麻烦使用 Jest。

这本书没有使用

create-react-app
或任何东西,而是从头开始构建React应用程序,所以我很难找到如何转换代码的相关示例。

当按照书中的方式以 React 17 风格编写时,测试通过。但如果我将

ReactDOM.render()
替换为
createRoot()
,测试就会失败。

我的应用程序目录如下所示:

├── package.json
├── package-lock.json
├── src
│   └── Appointment.js
└── test
    └── Appointment.test.js

文件内容为:

package.json

{
  "name": "appointments",
  "version": "1.0.0",
  "description": "Appointments project from Mastering React Test-Driven Development.",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "repository": {
    "type": "git",
    "url": "example.com"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/plugin-transform-runtime": "^7.18.6",
    "@babel/preset-env": "^7.18.6",
    "@babel/preset-react": "^7.18.6",
    "jest": "^28.1.2",
    "jest-environment-jsdom": "^28.1.3"
  },
  "dependencies": {
    "@babel/runtime": "^7.18.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "jest": {
    "testEnvironment": "jsdom"
  }
}

src/Appointment.js

import React from 'react';

export const Appointment = () => <div>Ashley</div>;

test/Appointment.test.js

import React from 'react';
import ReactDOM from 'react-dom';
// import {createRoot} from 'react-dom/client';

import {Appointment} from '../src/Appointment';

describe('Appointment', () => {
  it("renders the customer's first name.", () => {
    const customer = {firstName: 'Ashley'};
    const component = <Appointment customer={customer} />;
    const container = document.createElement('div');
    document.body.appendChild(container);

    ReactDOM.render(component, container);

    // const root = createRoot(container);
    // root.render(component);

    expect(document.body.textContent).toMatch('Ashley');
  });
});

使用

ReactDOM.render()
,测试通过,但出现以下错误:

  console.error
    Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot

      11 |     document.body.appendChild(container);
      12 |
    > 13 |     ReactDOM.render(component, container);
         |              ^
      14 |
      15 |     expect(document.body.textContent).toMatch('Ashley');
      16 |   });

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
      at Object.render (node_modules/react-dom/cjs/react-dom.development.js:29670:5)
      at Object.render (test/Appointment.test.js:13:14)

我查找了如何将

ReactDOM.render()
转换为
createRoot()
,并将测试更改为:

import React from 'react';
// import ReactDOM from 'react-dom';
import {createRoot} from 'react-dom/client';

import {Appointment} from '../src/Appointment';

describe('Appointment', () => {
  it("renders the customer's first name.", () => {
    const customer = {firstName: 'Ashley'};
    const component = <Appointment customer={customer} />;
    const container = document.createElement('div');
    document.body.appendChild(container);

    // ReactDOM.render(component, container);

    const root = createRoot(container);
    root.render(component);

    expect(document.body.textContent).toMatch('Ashley');
  });
});

测试失败如下:


> [email protected] test
> jest

 FAIL  test/Appointment.test.js
  Appointment
    ✕ renders the customer's first name. (9 ms)

  ● Appointment › renders the customer's first name.

    expect(received).toMatch(expected)

    Expected substring: "Ashley"
    Received string:    ""

      17 |     root.render(component);
      18 |
    > 19 |     expect(document.body.textContent).toMatch('Ashley');
         |                                       ^
      20 |   });
      21 | });
      22 |

      at Object.toMatch (test/Appointment.test.js:19:39)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:317:13)
      at runJest (node_modules/@jest/core/build/runJest.js:407:19)
      at _run10000 (node_modules/@jest/core/build/cli/index.js:339:7)
      at runCLI (node_modules/@jest/core/build/cli/index.js:190:3)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.979 s, estimated 1 s
Ran all test suites.

如何使用

createRoot()
通过此测试?

javascript reactjs testing jestjs
2个回答
8
投票

经过更多挖掘后,我发现

act()
可以在 React 18 中使用,强制在检查测试断言之前进行渲染。这允许测试立即运行,而无需在测试失败时等待 Jest 的 did() 超时。

在 Jest 配置中,

globals.IS_REACT_ACT_ENVIRONMENT
必须设置为
true
。我在这里更新了
package.json

package.json

...
  "jest": {
    "testEnvironment": "jsdom",
    "globals": {
      "IS_REACT_ACT_ENVIRONMENT": true
    }
  }
...

然后可以更新测试以使用

act()
中的
react-dom/test-utils
:

import React from 'react';
import {createRoot} from 'react-dom/client';
import {act} from 'react-dom/test-utils';

import {Appointment} from '../src/Appointment';

describe('Appointment', () => {
  it("renders the customer's first name.", () => {
    const customer = {firstName: 'Ashley'};
    const component = <Appointment customer={customer} />;
    const container = document.createElement('div');
    document.body.appendChild(container);

    const root = createRoot(container);

    act(() => root.render(component));

    expect(document.body.textContent).toMatch('Ashley');
  });
});

资源:


0
投票

React 17 看起来会在您调用时立即渲染

ReactDOM.render
:

const App = () => {
    return 'foo';
};

ReactDOM.render(<App />, document.querySelector('.react'));
console.log(document.querySelector('.react').textContent);
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div class='react'></div>

相比之下,React 18 则不然,它仅在任何其他代码完成后才执行渲染工作(通常会在几毫秒后):

const App = () => {
    return 'foo';
};

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
console.log(document.querySelector('.react').textContent.trim());
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>

这种差异肉眼无法察觉,但会导致测试失败,因为在

expect
运行时渲染尚未发生。

一种选择是使用测试的回调形式,以便您可以在渲染发生后调用

expect

it("renders the customer's first name.", (done) => {
    const component = <Appointment customer={ { firstName: 'Ashley' } } />;
    const container = document.body.appendChild(document.createElement('div'));
    createRoot(container).render(component);
    setTimeout(() => {
        expect(document.body.textContent).toMatch('Ashley');
        done();
    });
});

const App = () => {
    return 'foo';
};

ReactDOM.createRoot(document.querySelector('.react')).render(<App />);
setTimeout(() => {
    console.log(document.querySelector('.react').textContent.trim());
});
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div class='react'></div>

© www.soinside.com 2019 - 2024. All rights reserved.