首页 > 解决方案 > 警告:测试中 Slider 的更新未包含在 act(...) 中

问题描述

我尝试使用“react-test-renderer”测试一段代码时遇到以下错误

下面是我的测试

import React from "react";
import { act, create } from "react-test-renderer";
import { Slider } from "../../../../src/components/slider/Slider";

describe("SliderComponent", () => {
  // it("renders without crashing", (done) => {
  //   const component = create(<Slider loop={false} itemsPerPage={3} slidesCount={8} />);
  //   const tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();
  //   component.unmount();
  //   done();
  // });

  // it("changes active item after delay", done => {
  //   const sliderCount = jest.fn();
  //   const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={50} itemsPerPage={3}
  //                                    slidesCount={8} activeSlide={0} />);
  //   const tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();
  //   act(() => {
  //     setTimeout(() => {
  //       expect(sliderCount).toBeCalled();
  //       component.unmount();
  //       done();
  //     }, 51);
  //   });

  // });

  // it("active item is not changed before delay", done => {
  //   const sliderCount = jest.fn();
  //   const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={60} itemsPerPage={3}
  //                                    slidesCount={8} />);
  //   const tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();
  //   act(() => {
  //     setTimeout(() => {
  //       expect(sliderCount.mock.calls.length).toBe(0);
  //       component.unmount();
  //       done();
  //     }, 50);
  //   });

  // });

  // it("when loop is set to false, active item is not changed after delay", done => {
  //   const sliderCount = jest.fn();
  //   const component = create(<Slider onActiveItemChange={sliderCount} loop={false} delay={20} itemsPerPage={3}
  //                                    slidesCount={8} activeSlide={0} />);
  //   const tree = component.toJSON();
  //   expect(tree).toMatchSnapshot();
  //   act(() => {
  //     setTimeout(() => {
  //       expect(sliderCount.mock.calls.length).toBe(0);
  //       component.unmount();
  //       done();
  //     }, 30);
  //   });

  // });

  // it('has list items that on click changes list active item', done => {
  //   const sliderCount = jest.fn();
  //   const component = create(<Slider onActiveItemChange={sliderCount} loop={false} delay={1000} itemsPerPage={3}
  //                                    slidesCount={8} activeSlide={0} />);
  //   const testInstance = component.root;
  //   act(() => {
  //     testInstance.findAllByType('li')[1].props.onClick();
  //     expect(sliderCount).toHaveBeenCalled();
  //     component.unmount();
  //     done();

  //   })
  // });

  it("loop resumes after click", done => {
    const sliderCount = jest.fn();
    // const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={50} itemsPerPage={1}
    //   slidesCount={3} activeSlide={0} />);
     act(() => {
      const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={50} itemsPerPage={1}
        slidesCount={3} activeSlide={0} />);
        const testInstance = component.root;
        testInstance.findAllByType('li')[1].props.onClick(); 
        setTimeout(() => {
          expect(sliderCount).toBeCalled();
          component.unmount();
          done();
        }, 51);
    });
  });

});

注释掉的测试成功通过。但最后一次测试失败

  SliderComponent
    × loop resumes after click (25 ms)

  ● SliderComponent › loop resumes after click

    Can't access .root on unmounted test renderer

      81 |       const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={50} itemsPerPage={1}
      82 |         slidesCount={3} activeSlide={0} />);
    > 83 |         const testInstance = component.root;
         |                                        ^
      84 |         testInstance.findAllByType('li')[1].props.onClick();
      85 |         setTimeout(() => {
      86 |           expect(sliderCount).toBeCalled();

      at Object.root (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:17237:15)
      at __tests__/src/components/slider/Slider.test.tsx:83:40
      at batchedUpdates (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:13452:12)
      at act (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15280:14)
      at Object.<anonymous> (__tests__/src/components/slider/Slider.test.tsx:80:6)

如果我将代码移到函数const component = create(<Slider onActiveItemChange={sliderCount} loop={true} delay={50} itemsPerPage={1} slidesCount={3} activeSlide={0} />);之外,我会得到act()

  console.error
    Warning: An update to Slider 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 Slider (C:\Users\KOTIENO1\Desktop\Projects\SafcomProjects\WORK\platform-of-choice\src\components\slider\Slider.tsx:1072:5)



      at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:68:30)
      at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:44:5)
      at warnIfNotCurrentlyActingUpdatesInDEV (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15034:9)
      at dispatchAction (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7129:9)
      at Object.next (src/components/slider/Slider.tsx:1192:9)
      at Object.next (node_modules/rxjs/src/internal/Subscriber.ts:194:14)
      at SafeSubscriber.Object.<anonymous>.Subscriber._next (node_modules/rxjs/src/internal/Subscriber.ts:119:22)

下面是我要测试的代码

import React, { useEffect, useState } from "react";
import styled, { css } from "styled-components";
import { BehaviorSubject, Subject, timer } from "rxjs";
import { repeatWhen, take, takeUntil, takeWhile, tap } from "rxjs/operators";

interface ISliderProps {
  slidesCount: number;
  activeSlide?: number;
  onActiveItemChange?;
  itemsPerPage?: number;
  delay?: number;
  interval?: number;
  loop?: boolean;
}

const SliderWrapper = styled.ul`
  list-style-type: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  //background: red;
`;

const SliderItem = styled.li`
  float: left;
  background: #FFF;
  cursor: pointer;
  margin: 0.1rem;
  list-style: none;
  width: 0.25rem;
  height: 0.25rem;
  border: 1px solid #00943C;
  border-radius: 0.5rem;

  ${props => props.active && css`
    background: #00943C;
    cursor: default;
    width: 1.5rem;`
  }

  ${props => !props.active && css`
    &:hover {
      background: #00943C;
    }
  `
  }
`;

export const Slider = (props: ISliderProps) => {

  const {
    slidesCount,
    activeSlide = 0,
    onActiveItemChange = () => {
    },
    itemsPerPage = 1,
    delay = 5000,
    interval = 5000,
    loop = true
  } = props;

  const [currentActiveSlide, setCurrentActiveSlide] = useState(activeSlide);
  const sliderCount = Math.ceil(slidesCount / itemsPerPage);
  const timerStart$ = new BehaviorSubject(null);
  const timerStop$ = new Subject<null>();
  const timer$ = timer(delay, interval).pipe(
    takeWhile(() => loop),
    takeUntil(timerStop$),
    repeatWhen(() => timerStart$)
  );

  useEffect(() => {
    const timerSubscription = timer$.subscribe({
      next: (i) => {
        const nextSlide = i >= sliderCount ? i % sliderCount : i;
        onActiveItemChange(nextSlide);
        setCurrentActiveSlide(nextSlide);
      }
    });
    return () => timerSubscription.unsubscribe();
  }, []);

  const handleSliderItemClick = (i: number) => {
    setCurrentActiveSlide(i);
    onActiveItemChange(i);
    timerStop$.next(null);
    timer(delay).pipe(
      take(1),
      tap(() => timerStart$.next(null))
    );
  };

  const slides = Object.keys(new Array(sliderCount).fill("")).map(id => ({ id }));
  return (
    <>
      <SliderWrapper>
        {slides.map(({ id }, i) =>
          <SliderItem
            key={id}
            aria-label={"scroll page number " + id}
            onClick={() => handleSliderItemClick(i)}
            active={i === currentActiveSlide}
          />)}
      </SliderWrapper>
    </>
  );
};

具体来说,我试图覆盖测试中未覆盖的线 未覆盖的线

我已经在下面检查了测试时,导致 React 状态更新的代码应该被包装到 actReact 测试库 / Jest 更新没有被包装在 act(...)但我似乎无法在我的测试中找出问题.

尽管错误仍然存​​在,但测试正在通过

显示通过测试的照片

标签: javascriptreactjsjestjsreact-test-renderer

解决方案


推荐阅读