【问题标题】:React + Jest infinite loop only in testReact + Jest 无限循环仅在测试中
【发布时间】:2023-02-01 12:44:57
【问题描述】:

我的 react + nextJS 项目出现以下问题...

该组件是这样的:

import React, { FC, useCallback, useEffect, useState } from 'react';

import InputMask, { Props } from 'react-input-mask';
import {
  getPayersDetails,
  PayerCompany,
  PayerContact,
  PayerDocuments,
} from 'services';
import { Formik } from 'formik';

import { Field, Loading, Page, Tooltip } from 'components';
import { Button, IconButton, Typography } from '@mui/material';
import { TextField, TextFieldProps } from '@material-ui/core';
import { SvgSelfCheckout } from 'images';
import {
  FaEdit,
  FaFileInvoiceDollar,
  FaUserCheck,
  FaUserAltSlash,
} from 'react-icons/fa';

import theme from 'styles/theme';
import * as S from './styles';
import { useRouter } from 'next/router';
import { PAYER_HOME } from 'src/routes';

const PayersDetails: FC = () => {
  const [payerCompany, setPayerCompany] = useState<PayerCompany[]>([]);
  const [payerContact, setPayerContact] = useState<PayerContact[]>([]);
  const [payerDocument, setPayerDocument] = useState<PayerDocuments[]>([]);
  const [loading, setLoading] = useState(true);

  const [isActivePayer, setIsActivePayer] = useState(false);

  const router = useRouter();

  const getPayerDetails = useCallback(async (payerId: number) => {
    setLoading(true);
    const payerDetails = await getPayersDetails(payerId);
    setPayerCompany(payerDetails.payerCompany);
    setPayerDocument(payerDetails.payerDocument);
    setPayerContact(payerDetails.payerContact);

    setLoading(false);
  }, []);

  useEffect(() => {
    if (!router.isReady) {
      return;
    }
    const payerId = router.query.payerId as string;

    try {
      const safePayerId = parseInt(payerId);
      getPayerDetails(safePayerId);
    } catch (e) {
      router.push(PAYER_HOME);
      return;
    }
  }, [getPayerDetails, router]);

  const contact = payerContact.length > 0 ? payerContact[0] : null;

  const mobilePhone = payerContact
    .filter(contact => contact.contactType.contactTypeName === 'mobile')
    .map(contact => contact.value)[0];

  return (
    <Page
      title="Detalhes do pagador"
      pageTitle="Detalhes do pagador"
      pageSubtitle="Dados pessoais"
      pageSubitleColor={`${theme.palette.primary.light}`}
    >
      {loading ? (
        <Loading show={loading} />
      ) : (
        <Formik
          enableReinitialize={true}
          initialValues={{
            name: contact?.contactName,
            email: contact?.value,
          }}
          onSubmit={() => console.log('onSumit')}
        >
          ....
          )}
        </Formik>
      )}
    </Page>
  );
};

export default PayersDetails;

我正在尝试使用以下代码对其进行测试:

import { render, screen, fireEvent } from '@testing-library/react';
import { getCompany } from 'services/companies';
import { getPayersDetails } from 'services/payers';
import PayersImport from '.';
import { useRouter } from 'next/router';
import userEvent from '@testing-library/user-event';

jest.mock('services/payers', () => ({
  __esModule: true, // this property makes it work
  default: 'mockedDefaultExport',
  getPayersDetails: jest.fn(),
}));

jest.mock('next/router', () => ({
  useRouter: jest.fn().mockImplementation(() => ({
    route: '/',
    pathname: '',
    query: '',
    asPath: '',
  })),
}));

describe('payers details layout', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  describe('when rendering', () => {
    let getPayersDetailsMock;

    beforeEach(() => {
      getPayersDetailsMock = {
        ...
      };
      getPayersDetails.mockResolvedValue(getPayersDetailsMock);

      useRouter.mockImplementation(() => ({
        route: '/',
        pathname: '',
        isReady: true,
        query: { payerId: 1 },
        asPath: '',
      }));

      render(<PayersImport />);
    });

    it('Calls details api with the correct id', () => {
      expect(getPayersDetails).toHaveBeenCalledWith(1);
    });
  });
});

问题是: 当我们进入浏览器时,组件加载正常,但是当我开玩笑地运行它时,出现以下错误:

console.error
    Warning: An update to PayersDetails 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. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at PayersDetails (/home/thiago/finnet/repos/welcome/apps/lunapay-front/src/layouts/Payers/PayersDetails/index.tsx:29:43)

      43 |     setPayerContact(payerDetails.payerContact);
      44 |
    > 45 |     setLoading(false);
         |     ^
      46 |   }, []);
      47 |
      48 |   useEffect(() => {

我知道这只是一个警告,对我来说不是问题,但问题是它进入无限循环试图再次渲染。

我究竟做错了什么??

【问题讨论】:

    标签: reactjs jestjs next.js


    【解决方案1】:

    我解决了这个问题,但这不是最好的解决方案。

    基本上,渲染是由再次渲染的 &lt;Loading /&gt; 组件触发的。

    我所做的只是添加一个新属性,告诉我它是否已经被获取并取消循环。

    const [payerDetailsResponse, setPayerDetailsResponse] =
        useState<PayerDetailsResponse | null>(null);
      const [payerCompany, setPayerCompany] = useState<PayerCompany[]>([]);
      const [payerContact, setPayerContact] = useState<PayerContact[]>([]);
      const [payerDocument, setPayerDocument] = useState<PayerDocuments[]>([]);
      const [loading, setLoading] = useState(true);
    
      const [isActivePayer, setIsActivePayer] = useState(false);
    
      const router = useRouter();
    
      const getPayerDetails = useCallback(async (payerId: number) => {
        setLoading(true);
        const payerDetails = await getPayersDetails(payerId);
        setPayerDetailsResponse(payerDetails);
        setPayerCompany(payerDetails.payerCompany);
        setPayerDocument(payerDetails.payerDocument);
        setPayerContact(payerDetails.payerContact);
    
        setLoading(false);
      }, []);
    
      useEffect(() => {
        if (payerDetailsResponse) {
          return;
        }
    
        if (!router.isReady) {
          return;
        }
        const payerId = router.query.payerId as string;
    
        try {
          const safePayerId = parseInt(payerId);
    
          getPayerDetails(safePayerId);
        } catch (e) {
          router.push(PAYER_HOME);
          return;
        }
      }, [router, getPayerDetails]);
    

    抱歉无法提供更多帮助,但这是解决我的问题的方法:)

    【讨论】:

      【解决方案2】:

      有时jest会和useEffect陷入无限循环,如果是这种情况,你可以模拟一下

      import React from 'react'
      
      const mockUseEffect = jest.fn()
      
      jest.spyOn(React, 'useEffect').mockImplementation(mockUseEffect)
      

      并尝试和/或适应您的需求

      【讨论】:

        猜你喜欢
        • 2020-12-03
        • 2010-12-17
        • 2020-12-04
        • 2013-10-08
        • 1970-01-01
        • 2019-04-01
        • 2021-01-31
        • 2020-04-14
        • 2017-04-19
        相关资源
        最近更新 更多