Reactテストでのact非推奨警告の解決策
問題の説明
React 18でコンポーネントテストを実施中に、以下の矛盾する警告が発生します:
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(...)
IDE(TypeScript)からの警告:
The signature '(callback: () => void | Promise): Promise' of 'act' is deprecated.ts(6387)
React 18では状態更新が自動でバッチ処理されるため、act()
が不要になったという情報もあります。しかし実際には、act()
を削除するとテストが失敗します。この矛盾が主な問題点です。
主な原因は:
@testing-library/react
とreact
のバージョン不一致- 型定義ファイルの非互換性
- テストライブラリの未更新
解決方法
方法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.6
はact
をreact
パッケージからインポートするように変更react@18.3.1
でact
が正式にエクスポートされるよう修正- 型定義の非推奨警告は
@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コールを含む処理
更新後も警告が消えない場合のチェックリスト
node_modules
を削除して再インストール- 複数バージョンの
react
が依存関係にないか確認 - 型定義ファイル間の競合がないか確認(
@types/react-dom
など)