はじめに
Vitest, React-Testing-Libraryでダウンロード機能のテストを書くことがありましたが、いろいろつまずいたので備忘録も兼ねて書きます。
フロントはVite, Reactで構築しています。
テスト対象
aタグを生成して、プログラム側でクリック、その後削除するコードです。
素でaタグを書いたらええやんと思われるかもですが、ダウンロードの前に何かしらの処理がしたい場合には、下のように書くと便利ですね。
const DownloadForm = () => {
const handleDownload = () =>
// ...何らかの処理
const link = document.createElement("a")
link.href = "downloadUrl"
link.download = ""
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
return (
<button onClick={handleDownload}>ダウンロード</button>
)
}
Error: Not implemented: navigation
まずはボタンの表示などの簡単なテストから書き始めていると、下のエラーが出ました。何やらテストライブラリ側でNavigation(URL遷移)をサポートしていないそうなのです。(JSDomあたり?)
Error: Not implemented: navigation (except hash changes)
documentをモックにして解決
navigationが変化したことをテストするのではなく、aタグの生成、クリック、削除の一連の動作が行われたことをテストします。
ハマったのが、documentのモックコードの位置です。it内の先頭やbefore内に書くと、documentが機能しなくなってしまうために、React周りやDOM操作を伴うテストなどが動かなくなります。なので諸々の処理が終わった後にモックを作成し、テストを書いています。
※より良い案があれば教えてくださいませ。
it("should file download", async () => {
render(
<MemoryRouter>
<DownloadForm />
</MemoryRouter>,
)
// ダウンロードボタンをクリック
const downloadButton = screen.getByText("ダウンロード")
fireEvent.click(downloadButton)
// モックを作成
const link = Object.assign(document.createElement("a"), {
click: vi.fn(),
})
vi.spyOn(document, "createElement").mockImplementation(() => link)
vi.spyOn(document.body, "appendChild").mockImplementation(vi.fn())
vi.spyOn(document.body, "removeChild").mockImplementation(vi.fn())
// ダウンロードのテスト
await waitFor(() => {
expect(document.body.appendChild).toHaveBeenCalledWith(link)
expect(link.href).toBe(
"downloadUrl",
)
expect(link.click).toHaveBeenCalledTimes(1)
expect(document.body.removeChild).toHaveBeenCalledWith(link)
})
vi.restoreAllMocks() // モックを初期化
})
おわりに
テストのモックというと、MSWを使ったり、ライブラリの関数をモックするなどしかやったことがありませんでした。このようにブラウザ側の機能もモックすることを覚えると色々なケースに対応できそうです。