はじめに
今回、発生したWarningはこちらです。
Warning: An update to MemoScreen 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 */
Reactの状態更新や副作用がテスト中に適切に管理されていない可能性があるため、発生するWarningとなります。
こちらの解決方法となり至ってシンプルですが、個人的に勉強になったのでまとめたいと思います。
テストと実装
MemoScreen.test.tsx
import { describe, expect, test } from "vitest";
import { render, screen } from "@testing-library/react";
import { MemoScreen } from "../../screens/MemoScreen";
import { SpyUserRepository } from "../repository/SpyUserRepository";
describe('MemoScreenのテスト',() => {
test('MemoScreenがレンダリングされた時、userRepositoryのgetAllMemoを呼んでいる', () => {
const spyUserRepository = new SpyUserRepository()
render(<MemoScreen userRepository = {spyUserRepository}/>)
expect(spyUserRepository.getAllMemo_isCalled).toBeTruthy()
})
})
MemoSreen.tsx
import { DefaultUserRepository, UserRepository } from "../repository/UserRepository.ts";
import { useEffect, useState } from "react";
import { UserMemo } from "../type/TypeMemoRepository.ts";
type Props = {
userRepository?:UserRepository
}
export const MemoScreen = (
{
userRepository = new DefaultUserRepository()
}: Props) => {
const [memo, setMemo] = useState<UserMemo[]>([])
useEffect(() => {
const getAllMemo = async() => {
const res = await userRepository.getAllMemo()
setMemo(res)
}
getAllMemo()
},[])
return (
<>
<h2>Memo Record</h2>
</>
)
}
act とは?
Warningの内容を見るとact
でのwrapを推奨されています。
act
とは何なのでしょうか?
act
:Reactの状態や副作用が安定するのを待ってからテストを行うためのユーティリティ。これにより、テスト中に発生する可能性のある非同期状態の変更が完了するのを待ってからアサーション(検証)を行うための関数です。
実際にact
でwrap
MemoScreen.test.tsx
import { describe, test, expect } from "vitest";
import { render, screen, act } from "@testing-library/react";
import { MemoScreen } from "../../screens/MemoScreen";
import { SpyUserRepository } from "../repository/SpyUserRepository";
describe('MemoScreenのテスト', () => {
test('MemoScreenがレンダリングされた時、userRepositoryのgetAllMemoを呼んでいる', () => {
act(() => {
render(<MemoScreen userRepository = {spyUserRepository}/>)
})
expect(spyUserRepository.getAllMemo_isCalled).toBeTruthy()
})
})
悲報
まだWarningが消えません
なぜWarningが消えない?
act
はレンダリングに対する非同期処理完了を待っていて、今回の実装側ではuseEffect
内でasync
関数を使ってデータ取得を行っているため、レンダリング後の非同期処理完了を待つ必要があり、Warningが消えなかったと思います。
解決方法
waitFor
を使用する方法で、今回のWarningを消すことができました!
MemoScreen.test.tsx
import { describe, test, expect } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { MemoScreen } from "../../screens/MemoScreen";
import { SpyUserRepository } from "../repository/SpyUserRepository";
describe('MemoScreenのテスト', () => {
test('MemoScreenがレンダリングされた時、userRepositoryのgetAllMemoを呼んでいる', async() => {
const spyUserRepository = new SpyUserRepository()
render(<MemoScreen userRepository = {spyUserRepository}/>)
await waitFor(() => {
expect(spyUserRepository.getAllMemo_isCalled).toBeTruthy()
})
})
})
最後に
waitFor
が1つのテストファイルに複数あると、テストが重くなりパフォーマンス低下につながってしまうので、テストファイルを細かく切り出すという少しプリミティブなやり方をしていますが、最もフェティッシュな方法を見つけ出したいですね。