TaikiTkwkbysh
@TaikiTkwkbysh (WAKA Engineer)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

Jestのエラーの解決方法を教えてください。

解決したいこと

reactのjestを現在学習中の者です。

参考書にて以下のコードのテストを行なっているのですが、
テストは正常終了するものの、ワーニングエラーが出てしまいます。

参考書に詳しい解説がないので、ご存じの方がいらっしゃいましたらご教示いただきたいです。

発生している問題・エラー①

    console.error
      Warning: An update to DelayInput 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 onChange (/Users/taikiwakabayashi/Desktop/React&Next/Practice_Project/samples/components/DeleyInput/index.tsx:9:13)

      30 |             timerRef.current = null;
      31 |
    > 32 |             setIsTyping(false);
         |             ^
      33 |
      34 |             setViewValue(e.target.value);
      35 |

発生している問題・エラー②

    console.error
      Warning: An update to DelayInput 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 onChange (/Users/taikiwakabayashi/Desktop/React&Next/Practice_Project/samples/components/DeleyInput/index.tsx:9:13)

      32 |             setIsTyping(false);
      33 |
    > 34 |             setViewValue(e.target.value);
         |             ^
      35 |
      36 |             onChange(e);
      37 |         }, 1000);

テスト対象のコード

import React, { useState, useCallback, useRef } from "react";

type DelayButtonProps = {
    onChange: React.ChangeEventHandler<HTMLInputElement>;
}

export const DelayInput = (props: DelayButtonProps) => {

    const { onChange } = props;
    const [isTyping, setIsTyping] = useState<boolean>(false);
    const [inputValue, setInputValue] = useState<string>('');
    const [viewValue, setViewValue] = useState<string>('');

    const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

    const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {

        setIsTyping(true);

        setInputValue(e.target.value);

        if(timerRef.current !== null) {
            clearTimeout(timerRef.current);
        }

        timerRef.current = setTimeout(() => {
            timerRef.current = null;

            setIsTyping(false);

            setViewValue(e.target.value);

            onChange(e);
        }, 1000);
        
    }, [onChange])

    const text = isTyping ? '入力中...' : `入力したテキスト:${viewValue}`;
    return (
        <div>
            <input type="text" data-testid="input-text" value={inputValue} onChange={handleChange} />
            <span data-testid="display-text">{text}</span>
        </div>
    )
}

テストコード

import { render, screen, RenderResult, fireEvent, act, waitFor } from "@testing-library/react";
import { DelayInput } from "./index";

describe('DelayInput', () => {
    let renderResult: RenderResult;
    let handleChange: jest.Mock;

    beforeEach(() => {
        jest.useFakeTimers();
        handleChange = jest.fn();
        renderResult = render(<DelayInput onChange={handleChange}></DelayInput>)
    });

    afterEach(() => {
        jest.runOnlyPendingTimers();
        jest.useRealTimers();
        renderResult.unmount();
    });


    it('should display input text 1second after onChange event occurs', async () => {
        const inputNode = screen.getByTestId('input-text') as HTMLInputElement;

        const inputText = 'Test Input Text';

        fireEvent.change(inputNode, {target: {value: inputText}});

        await act(() => {
            jest.runAllTimers();
        })

        const spanNode = screen.getByTestId('display-text') as HTMLSpanElement;

        expect(spanNode).toHaveTextContent(`入力したテキスト:${inputText}`);

    })

    it('should call onChange 1second after onChange event occurs', async () => {

        const inputText = 'Test Input Text';

        const inputNode = screen.getByTestId('input-text') as HTMLInputElement;

        fireEvent.change(inputNode, {target: {value: inputText}});

        await act(() => {
            jest.runAllTimers();
        });
        
        expect(handleChange).toHaveBeenCalled();
    })

});

環境

■macOs Monterey - version 12.6

■node.js : v19.2.0

■npm : 8.19.3

■next.js : 13.0.5

■react : 18.2.0

■typescript : 4.9.3

■jest : ^29.3.1

■jest-environment-jsdom : ^29.3.1

■jest.setup.js

import '@testing-library/jest-dom/extend-expect';

■jest.config.js

const nextJest = require('next/jest');

const createJestConfig = nextJest({ dir: './' });

const customJestConfig = {
    testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules'],
    setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
    testEnvironment: 'jest-environment-jsdom',
}

module.exports = createJestConfig(customJestConfig);
0

2Answer

waitForでくくってみてください

//...
const spanNode = screen.getByTestId('display-text') as HTMLSpanElement;

await waitFor(() => {
  expect(spanNode).toHaveTextContent(`入力したテキスト:${inputText}`);
})


0Like

@Inp
この度はご対応頂き、誠にありがとうございます。

ご教示頂いた通りに実行してみたのですが、変わらず同じエラーが出てしまいました。

■修正ソース

    it('should display input text 1second after onChange event occurs', async () => {
        const inputNode = screen.getByTestId('input-text') as HTMLInputElement;

        const inputText = 'Test Input Text';

        fireEvent.change(inputNode, {target: {value: inputText}});

        await act(() => {
            jest.runAllTimers();
        })

        const spanNode = screen.getByTestId('display-text') as HTMLSpanElement;

        await waitFor(() => {
            expect(spanNode).toHaveTextContent(`入力したテキスト:${inputText}`);
        })
    })


    it('should call onChange 1second after onChange event occurs', async () => {

        const inputText = 'Test Input Text';

        const inputNode = screen.getByTestId('input-text') as HTMLInputElement;

        fireEvent.change(inputNode, {target: {value: inputText}});

        await act(() => {
            jest.runAllTimers();
        });
        
        await waitFor(() => {
            expect(handleChange).toHaveBeenCalled();
        })
    })

■エラー

    console.error
      Warning: An update to DelayInput 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 onChange (/Users/username/Desktop/React&Next/NextJS_practice/samples/components/DeleyInput/index.tsx:9:13)

      30 |             timerRef.current = null;
      31 |
    > 32 |             setIsTyping(false);
         |             ^
      33 |
      34 |             setViewValue(e.target.value);
      35 |

      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 warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
      at dispatchSetState (node_modules/react-dom/cjs/react-dom.development.js:17527:7)
      at setIsTyping (components/DeleyInput/index.tsx:32:13)
      at callTimer (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:745:24)
      at doTickInner (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1307:29)
      at doTick (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1388:20)
      at Object.tick (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1396:20)
      at Object.runToLast (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1552:26)
      at FakeTimers.runOnlyPendingTimers (node_modules/@jest/fake-timers/build/modernFakeTimers.js:55:19)
      at Object.runOnlyPendingTimers (components/DeleyInput/index.spec.tsx:15:14)

    console.error
      Warning: An update to DelayInput 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 onChange (/Users/username/Desktop/React&Next/NextJS_practice/samples/components/DeleyInput/index.tsx:9:13)

      32 |             setIsTyping(false);
      33 |
    > 34 |             setViewValue(e.target.value);
         |             ^
      35 |
      36 |             onChange(e);
      37 |         }, 1000);

      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 warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
      at dispatchSetState (node_modules/react-dom/cjs/react-dom.development.js:17527:7)
      at setViewValue (components/DeleyInput/index.tsx:34:13)
      at callTimer (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:745:24)
      at doTickInner (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1307:29)
      at doTick (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1388:20)
      at Object.tick (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1396:20)
      at Object.runToLast (node_modules/@sinonjs/fake-timers/src/fake-timers-src.js:1552:26)
      at FakeTimers.runOnlyPendingTimers (node_modules/@jest/fake-timers/build/modernFakeTimers.js:55:19)
      at Object.runOnlyPendingTimers (components/DeleyInput/index.spec.tsx:15:14)

パッケージが足りないのでしょうか。

Package.json

{
  "name": "samples",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "test": "jest"
  },
  "dependencies": {
    "next": "13.0.5",
    "react": "18.2.0",
    "react-dom": "18.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.20.5",
    "@mdx-js/react": "^1.6.22",
    "@storybook/addon-actions": "^6.5.13",
    "@storybook/addon-essentials": "^6.5.14",
    "@storybook/addon-interactions": "^6.5.13",
    "@storybook/addon-links": "^6.5.13",
    "@storybook/builder-webpack5": "^6.5.13",
    "@storybook/manager-webpack5": "^6.5.13",
    "@storybook/react": "^6.5.13",
    "@storybook/testing-library": "^0.0.13",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@types/mdx": "^2.0.3",
    "@types/node": "18.11.9",
    "@types/react": "18.0.25",
    "@types/react-dom": "18.0.9",
    "@types/styled-components": "^5.1.26",
    "babel-loader": "^8.3.0",
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.5",
    "eslint-plugin-storybook": "^0.6.7",
    "jest": "^29.3.1",
    "jest-environment-jsdom": "^29.3.1",
    "sb": "^6.5.13",
    "styled-components": "^5.3.6",
    "typescript": "4.9.3"
  },
  "overrides": {
    "@mdx-js/react": {
      "react": "$react"
    }
  }
}

0Like

Your answer might help someone💌