Skip to content

Reactテストでのact非推奨警告の解決策

問題の説明

React 18でコンポーネントテストを実施中に、以下の矛盾する警告が発生します:

  1. Jestから表示される警告:

    Warning: An update to ContactFormController inside a test was not wrapped in act(...). 
    When testing, code that causes React state updates should be wrapped into act(...)
  2. IDE(TypeScript)からの警告:

    The signature '(callback: () => void | Promise): Promise' of 'act' is deprecated.ts(6387)

React 18では状態更新が自動でバッチ処理されるため、act()が不要になったという情報もあります。しかし実際には、act()を削除するとテストが失敗します。この矛盾が主な問題点です。

主な原因は:

  • @testing-library/reactreactのバージョン不一致
  • 型定義ファイルの非互換性
  • テストライブラリの未更新

解決方法

方法1: ライブラリのバージョン統一(推奨)

パッケージを以下のバージョンに更新

bash
npm install react@18.3.1 @testing-library/react@15.0.6 @types/react@18.3.1
# または
yarn add react@18.3.1 @testing-library/react@15.0.6 @types/react@18.3.1

変更後のpackage.json:

json
{
  "dependencies": {
    "react": "^18.3.1",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^15.0.6",
    "@testing-library/user-event": "^14.5.2"
  },
  "devDependencies": {
    "@types/react": "^18.3.1"
  }
}

なぜこの方法が有効か

  • @testing-library/react@15.0.6actreactパッケージからインポートするように変更
  • react@18.3.1actが正式にエクスポートされるよう修正
  • 型定義の非推奨警告は@types/react@18.3.1で解消

方法2: actのインポート元を変更

js
// 修正後のインポート
import { act } from 'react' // react-dom/test-utils ではない

注意点

この修正は React 18.3.1以上 でのみ有効です。古いバージョンでは機能しません。

不要なパッケージの削除

react-test-rendererがインストールされている場合、非推奨警告の原因となることがあります:

bash
npm uninstall react-test-renderer
# または
yarn remove react-test-renderer

最適化されたテストコード例

ライブラリ更新後にactの明示的な使用を避ける書き方:

jsx
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ContactFormController from '.';

describe('Contact Form Controller tests', () => {
  it('should render loader and disable submit button on form submit', async () => {
    const user = userEvent.setup();
    render(<ContactFormController />);
    
    const submitBtn = screen.getByTestId('contact-submit-button');
  
    // actを明示せずに非同期イベントを実行
    await user.type(screen.getByLabelText(/^First Name/i), 'Captain');
    await user.type(screen.getByLabelText(/^Last Name/i), 'Bob');
    await user.type(screen.getByLabelText(/^Email/i), 'captain@bob.com');
    await user.type(screen.getByPlaceholderText(/^Greetings/i), 'Captain Ahoy');
    await user.click(submitBtn);
    
    expect(screen.getByRole('alert')).toBeInTheDocument();
    expect(submitBtn).toBeDisabled();
  });
});

ポイント

  • userEventは常にawaitで扱う:すべてのイベントが非同期処理として完了するのを保証
  • actの明示は不要@testing-library/reactが内部で適切に処理
  • userEvent.setup()の使用:最新の推奨パターン

技術的な背景

React 18のactの挙動変更

  • React 17以前:テスト内の状態更新を明示的にact()でラップする必要があった
  • React 18以降:テスト環境が自動的に更新をバッチ処理
  • 例外:非同期操作(ユーザーイベント、タイマーなど)は依然として処理待ちが必要

非推奨警告の真の原因

ts
// 非推奨になった型定義(@testing-library/react 古いバージョン)
declare function act(callback: () => void | Promise<void>): Promise<void>;

↓ 正しい型定義(react 18.3.1)

ts
export function act(callback: () => void): void;
export function act(callback: () => Promise<void>): Promise<void>;

ライブラリ更新で、この不一致が解消されます。

よくある質問

「React 18ではactが必要ない」という情報は正しい?

部分的に正解です。状態更新のバッチ処理は自動化されましたが、以下の場合は明示的な処理が必要:

  • 非同期ユーザー操作(入力、クリックなど)
  • タイマーを使用するコンポーネント
  • 外部APIコールを含む処理

更新後も警告が消えない場合のチェックリスト

  1. node_modulesを削除して再インストール
  2. 複数バージョンのreactが依存関係にないか確認
  3. 型定義ファイル間の競合がないか確認(@types/react-domなど)