act は、アサーションを行う前に保留中の React の更新を適用するためのテストヘルパーです。

await act(async actFn)

コンポーネントをアサーションの準備をするには、レンダリングと更新を実行するコードを await act() 呼び出しでラップします。これにより、テストがブラウザでの React の動作に近い形で実行されます。

注意

act() を直接使用すると、少し冗長になる場合があります。定型的なコードを避けるために、React Testing Library のようなライブラリを使用することができます。そのヘルパーは act() でラップされています。


リファレンス

await act(async actFn)

UI テストを書くとき、レンダリング、ユーザーイベント、データフェッチなどのタスクは、ユーザーインターフェースとのインタラクションの「単位」とみなすことができます。React は、これらの「単位」に関連するすべての更新が処理され、アサーションを行う前に DOM に適用されていることを確認するための act() というヘルパーを提供しています。

act という名前は、Arrange-Act-Assert パターンに由来しています。

it ('renders with button disabled', async () => {
await act(async () => {
root.render(<TestComponent />)
});
expect(container.querySelector('button')).toBeDisabled();
});

注意

act は、awaitasync 関数と共に使用することをお勧めします。同期バージョンは多くの場合に機能しますが、すべての場合に機能するわけではなく、React が内部で更新をスケジュールする方法により、同期バージョンをいつ使用できるかを予測するのは困難です。

今後、同期バージョンは非推奨となり、削除される予定です。

パラメーター

  • async actFn: テスト対象のコンポーネントのレンダリングまたはインタラクションをラップする非同期関数。actFn 内でトリガーされた更新はすべて、内部の act キューに追加され、その後、それらがまとめてフラッシュされ、DOM への変更が処理および適用されます。非同期であるため、React は非同期境界をまたぐコードも実行し、スケジュールされた更新をフラッシュします。

戻り値

act は何も返しません。

使用法

コンポーネントをテストするときは、act を使用して、その出力に関するアサーションを行うことができます。

たとえば、この Counter コンポーネントがあるとします。以下の使用例は、それをテストする方法を示しています。

function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => prev + 1);
}

useEffect(() => {
document.title = `You clicked ${this.state.count} times`;
}, [count]);

return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.handleClick}>
Click me
</button>
</div>
)
}

テストにおけるコンポーネントのレンダリング

コンポーネントのレンダリング出力をテストするには、レンダリングをact()でラップします。

import {act} from 'react';
import ReactDOMClient from 'react-dom/client';
import Counter from './Counter';

it('can render and update a counter', async () => {
container = document.createElement('div');
document.body.appendChild(container);

// ✅ Render the component inside act().
await act(() => {
ReactDOMClient.createRoot(container).render(<Counter />);
});

const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
});

ここでは、コンテナを作成し、ドキュメントに追加し、Counterコンポーネントをact()内でレンダリングします。これにより、アサーションを行う前に、コンポーネントがレンダリングされ、その効果が適用されることが保証されます。

actを使用することで、アサーションを行う前にすべての更新が適用されていることが保証されます。

テストでのイベントの発行

イベントをテストするには、イベント発行をact()でラップします。

import {act} from 'react';
import ReactDOMClient from 'react-dom/client';
import Counter from './Counter';

it.only('can render and update a counter', async () => {
const container = document.createElement('div');
document.body.appendChild(container);

await act( async () => {
ReactDOMClient.createRoot(container).render(<Counter />);
});

// ✅ Dispatch the event inside act().
await act(async () => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});

const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});

ここでは、actでコンポーネントをレンダリングし、別のact()内でイベントを発行します。これにより、イベントからのすべての更新がアサーションを行う前に適用されることが保証されます。

落とし穴

DOMイベントの発行は、DOMコンテナがドキュメントに追加されている場合にのみ機能することに注意してください。 React Testing Libraryのようなライブラリを使用すると、ボイラープレートコードを減らすことができます。

トラブルシューティング

エラーが出ています: “現在のテスト環境はactをサポートするように構成されていません(…)”

actを使用するには、テスト環境でglobal.IS_REACT_ACT_ENVIRONMENT=trueを設定する必要があります。これは、actが正しい環境でのみ使用されるようにするためです。

グローバルを設定しない場合、次のようなエラーが表示されます。

コンソール
警告: 現在のテスト環境はact(…)をサポートするように構成されていません。

修正するには、Reactテスト用のグローバルセットアップファイルにこれを追加してください。

global.IS_REACT_ACT_ENVIRONMENT=true

注意

React Testing Libraryのようなテストフレームワークでは、IS_REACT_ACT_ENVIRONMENTはすでに設定されています。