测试时,导致React状态更新的代码应包装为act(…)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了测试时,导致React状态更新的代码应包装为act(…)相关的知识,希望对你有一定的参考价值。

问题不是新问题,我已经在stackoverflow上看到了类似的问题,但是最多没有解决。即使通过测试,我也会收到此警告。试图通过将act(() => {});包装在那些由于状态更改而导致的语句上来解决问题,但无法确定我应该在哪里使用它。任何帮助将不胜感激。我的错误消息:

● Console

console.error node_modules/react-dom/cjs/react-dom.development.js:88
  Warning: An update to CompanyDetail inside a test was not wrapped in act(...).

  When testing, code that causes React state updates should be wrapped into act(...):

  act(() => {
    /* fire events that update state */
  });
  /* assert on the output */

  This ensures that you're testing the behavior the user would see in the browser. 
      in CompanyDetail (at CompanyDetail.test.js:53)
      in Router (created by BrowserRouter)
      in BrowserRouter (at CompanyDetail.test.js:52)
console.error node_modules/react-dom/cjs/react-dom.development.js:88
  Warning: An update to CompanyDetail inside a test was not wrapped in act(...).

  When testing, code that causes React state updates should be wrapped into act(...):

  act(() => {
    /* fire events that update state */
  });
  /* assert on the output */

  This ensures that you're testing the behavior the user would see in the browser.
      in CompanyDetail (at CompanyDetail.test.js:53)
      in Router (created by BrowserRouter)
      in BrowserRouter (at CompanyDetail.test.js:52)

CompanyDetails.js的代码

import React from 'react';
import { Row } from 'react-bootstrap';
import CONSTANTS from '../../constants/constants';
import { Link } from 'react-router-dom';
import useFetch from '../../effects/use-fetch.effect';
import Spinner from '../spinner/Spinner';

const CompanyDetail = (props) => {
  const { id } = props.match.params;

  const [{ name, description, jobs, known_to_sponsor_visa }, isLoading] = useFetch(
    `${CONSTANTS.BASE_URL}/companies/${id}`
  );
  let classes = 'badge badge-';
  classes += known_to_sponsor_visa === 'Yes' ? 'success' : 'danger';
  return (
    <React.Fragment>
      {!isLoading ? (
        <Row style={{ margin: '20px' }}>
          <div className="col-lg-6 col-md-6">
            <div className="company-details">
              <h1 data-testid="company-name">{name ? name : 'No name provided'}</h1>
              <h5 data-testid="sponsors-visa">
                Known to sponsor work visa: {''}
                {known_to_sponsor_visa ? <span className={classes}> {known_to_sponsor_visa}</span> : 'No data'}
              </h5>
              <p data-testid="description">{description ? description : 'No description'}</p>
            </div>
          </div>
          <div className="col-lg-6 col-md-6 ">
            <div className="card-box ">
              <div className="card-content content-primary">
                <h3 className="card-title" data-testid="jobs">
                  {!jobs ? 'No jobs posted' : ` Last ${jobs.length} Jobs`}
                </h3>
                <ul>
                  {jobs
                    ? jobs.map((job) => {
                        return (
                          <Link to={`/jobs/${job.job_id}`} data-testid="individual-job-link" key={job.job_id}>
                            <li data-testid="individual-job" key={job.job_id}>
                              {job.position}
                            </li>
                          </Link>
                        );
                      })
                    : 'No jobs posted'}
                </ul>
              </div>
            </div>
          </div>
        </Row>
      ) : (
        <div data-testid="spinner">
          <Spinner />
        </div>
      )}
    </React.Fragment>
  );
};

export default CompanyDetail;

CompanyDetail.test.js的代码

import React from 'react';
import CompanyDetail from '../../../components/company-detail/CompanyDetail';
import { BrowserRouter } from 'react-router-dom';
import { render, waitFor, fireEvent, act } from '@testing-library/react';
describe('Company Details', () => {
  let mockData;
  let data = {
    name: 'Google',
    description: 'this is a company description',
    known_to_sponsor_visa: 'Yes',
    id: '4',
    jobs: [{ job_id: '2', position: 'Web developer' }],
  };

  beforeEach(() => {
    jest.spyOn(global, 'fetch').mockResolvedValue({ json: jest.fn().mockResolvedValue({ data }) });
    mockData = { match: { params: { id: 4 } } };
  });

  it('renders company details with given data', async () => {
    const { getByTestId } = render(
      <BrowserRouter>
        <CompanyDetail {...mockData} key={data.jobs[0].job_id} />,
      </BrowserRouter>
    );

    await waitFor(() => expect(getByTestId('company-name').textContent).toMatch('Google'));
    await waitFor(() => expect(getByTestId('sponsors-visa').textContent).toMatch('Yes'));
    await waitFor(() => expect(getByTestId('description').textContent).toMatch('this is a company description'));
  });

  it('renders correct jobs length', async () => {
    const { getByTestId } = render(
      <BrowserRouter>
        <CompanyDetail {...mockData} key={data.jobs[0].job_id} />,
      </BrowserRouter>
    );
    await waitFor(() => expect(getByTestId('jobs').textContent).toMatch('1'));
  });

  it('renders jobs', async () => {
    const { getAllByTestId } = render(
      <BrowserRouter>
        <CompanyDetail {...mockData} key={data.jobs[0].job_id} />,
      </BrowserRouter>
    );
    await waitFor(() => getAllByTestId('individual-job').map((li) => li.textContent));
  });

  it('renders spinner when there is no jobs', () => {
    const { getByTestId } = render(
      <BrowserRouter>
        <CompanyDetail {...mockData} />,
      </BrowserRouter>
    );
    expect(getByTestId('spinner').children.length).toBe(1);
  });

  it('navigates to individual job page', async () => {
    const { getByText } = render(
      <BrowserRouter>
        <CompanyDetail {...mockData} />
      </BrowserRouter>
    );
    await waitFor(() => fireEvent.click(getByText(data.jobs[0].position)));
    await waitFor(() => expect(document.querySelector('a').getAttribute('href')).toBe(`/jobs/${data.jobs[0].job_id}`));
  });
});
答案

实际上我解决了,这是我的小错误。我忘记将Spinner测试包装在asyncawait中。下面的代码解决了它。

it('renders spinner when there is no jobs', async () => {
const { getByTestId } = render(
  <BrowserRouter>
    <CompanyDetail {...mockData} />,
  </BrowserRouter>
);
await waitFor(() => expect(getByTestId('spinner').children.length).toBe(1));

});

以上是关于测试时,导致React状态更新的代码应包装为act(…)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 React-Router 中的路由之间共享状态?

一步延迟更新导致 React State

警告:测试中对 App 的更新未包含在酶和钩子中的 act(...) 中

路由器事件在构造函数上使用时会导致错误警告:无法对未安装的组件执行 React 状态更新

如何在 React 中保存嵌套对象的状态?

setState 不会更新 React 中的子级