「Test Isolation with React」という記事を読んだ
テストは他のテストに影響を与えないようにちゃんと分離しましょうという記事。 先行するテストに依存してはいけない。(例えば先行するテストでrenderしといて後続でそれを利用するとか。変数セットしといて後のやつが利用するとか。)
この記事でも、テストは機能ごとに着目するよりユースケースに着目して書いたほうが良いよということが書かれていた。
記事
「Write fewer, longer tests」という記事を読んだ
Reactのテストでa test was not wrapped in act(...)が出た
const user = userEvent.setup(); render(<Component />); const link = screen.getByRole("link"); await user.click(link) expect(...)
のようなコードを書いていたらタイトルのようなWarningが出た。 割とよくあることなのか解決方法も同時に出てstateを更新するイベントを発火するやつはactで囲めと書いてある。
ただ、単にactで囲んでact(async() => await user.click(link))でいけるかと思いきや解決しなかった。
react-act-examples/sync.md at master · threepointone/react-act-examples · GitHub
を見ると、actがPromiseを返すのでこれをawaitで待ってやれば良さそうだった。 つまり、
const user = userEvent.setup(); render(<Component />); const link = screen.getByRole("link"); await act(() => user.click(link)) expect(...)
こう書くとWarningが出ずにテストが通った。
うまくいかないときにError: Not implemented: navigation (except hash changes)というエラーが出たりもして困っていたがこれも上記で同時に解決した。
React + Jest + jsdomでfetchが絡むテストを実行するとTypeErrorが出る
node_modules/jsdom/lib/jsdom/browser/Window.js:376 return idlUtils.wrapperForImpl(idlUtils.implForWrapper(window._document)._location); ^ TypeError: Cannot read properties of null (reading '_location')
React + Jestでテストを書いているとこういうエラーが出た。スタックトレースを見ると、mswjsから来てる。 問題切り分けてると非同期処理(fetch)があるときに、waitForで再描画待たずにテストを終わらせるとこれが出るっぽい。
追記
これが出るときもあれば出ないときもある不安定な状態になった・・・
さらに追記
これを踏んでたっぽい。自分しか使わないプロダクトなので一旦
node v18 で導入された global fetch を jsdom 環境下の jest で利用する方法 - Qiita
これで回避することにした。
fetchを呼び出してるReactコンポーネントのテストをしたい(調査中メモ)
import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { useEffect } from 'react'; function Heading({ callback }: { callback: () => void }) { useEffect(() => { async function f() { await fetch('https://localhost/'); callback(); } f(); }, [callback]); return <h1>Hello</h1>; } describe('Heading', () => { it('sample test', async () => { const mock = jest.fn(); render(<Heading callback={mock} />); expect(screen.getByRole('heading', { name: 'Hello' })).toBeInTheDocument(); waitFor(() => expect(mock).toHaveBeenCalled()); }); });
サンプルでこのコードに特に意味はない。最初にコンポーネントがレンダリングされるときにAPI経由で何かデータ取ってきて それを表示したい、そのためのテストコードのサンプル。 これをテスト実行するとReferenceError: fetch is not definedが出る。testEnvironment: "jsdom"だとこうなるらしい。 Node18なのでnode環境ならfetchはあるはず。
調べているとテストにはMock Service Workerを使えというのやjest-fetch-mockを使えというのがあった。これから試してみる。
JestでNode.jsのfetchをテストしたい
背景
内部でfetchを呼んでいる関数をテストしたいです。 Nodeのv18以降ではnode-fetchなしでfetchが使えるようになりましたが、node-fetchを使っているサンプルが多く組み込みのfetchを使っているサンプルが少ないので調べました。
結論
node:testモジュールのmock.methodを使うか、Jestのjest.spyOnを使います。
mock.methodの場合
import { mock } from "node:test" // テスト対象メソッド。成功すると"Hello"が返ってくる。 async function fetchData() { const res = await fetch("https://example.com/"); if (!res.ok) { return "NG"; } return res.text(); } it("fetchに成功するとHelloを返す", async () => { mock.method(global, "fetch", () => Promise.resolve(new Response("Hello")); const result = await fetchData(); expect(result).toBe("Hello"); mock.reset(); });
jest.spyOnの場合
// テスト部分のみ it("fetchに成功するとHelloを返す", async () => { const spy = jest.spyOn(global, "fetch"); spy.mockImplementation(() => Promise.resolve(new Response("Hello"))); const result = await fetchData(); expect(result).toBe("Hello"); spy.mockRestore(); });
fetchで一定時間応答がなければタイムアウトしたい
AbortSignal.timeout()が使えます。
// 10秒でタイムアウト const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
タイムアウトすると、TimeoutErrorが例外で投げられるのでこれをcatchして処理します。