この記事はAteam LifeDesign Advent Calendar 2024の11日目記事です
はじめに
5周遅れぐらいですがアドカレの機会を使ってBunのtest周りを触ります
多いであろうNextJS+jestの構成からBunでのtestに移行しある程度Passするところまでやります
セットアップ
NextJS + jestの準備
まずは https://nextjs.org/docs/app/building-your-application/testing/jest を参考にNextJS + jestの環境を用意します
# PJ作成
npx create-next-app@latest --example with-jest with-jest-app
# 起動テスト
cd with-jest/
npm run dev
# テスト実行
npm test
# コンソールエラーが出て見づらいのでパッケージをアップデート
npm outdated
npm uninstall @testing-library/react
npm install -D @testing-library/react
# 記録用にテスト実行
npm test
PASS app/utils/add.test.ts
PASS app/page.test.tsx
PASS app/blog/[slug]/page.test.tsx
PASS __tests__/snapshot.tsx
PASS app/counter.test.tsx
PASS __tests__/index.test.tsx
Test Suites: 6 passed, 6 total
Tests: 6 passed, 6 total
Snapshots: 1 passed, 1 total
Time: 3.243 s
Ran all test suites.
bun testに移行する
https://bun.sh/ をinstallします
curl -fsSL https://bun.sh/install | bash
exec /bin/zsh
bun --help
# テスト実行
bun test
この4行でテスト実行可能なところまでいけます
実行すると以下のエラー
bun test v1.1.38 (bf2f153f)
app/page.test.tsx:
234 | throw error;
235 | }
236 | if (!baseElement) {
237 | // default to document.body instead of documentElement to avoid output of potentially-large
238 | // head elements (such as JSS style blocks) in debug output
239 | baseElement = document.body;
^
ReferenceError: Can't find variable: document
at render (/workspace/with-jest-app/node_modules/@testing-library/react/dist/pure.js:239:19)
at /workspace/with-jest-app/app/page.test.tsx:8:3
✗ App Router: Works with Server Components [3.47ms]
app/counter.test.tsx:
234 | throw error;
235 | }
236 | if (!baseElement) {
237 | // default to document.body instead of documentElement to avoid output of potentially-large
238 | // head elements (such as JSS style blocks) in debug output
239 | baseElement = document.body;
^
ReferenceError: Can't find variable: document
at render (/workspace/with-jest-app/node_modules/@testing-library/react/dist/pure.js:239:19)
at /workspace/with-jest-app/app/counter.test.tsx:8:3
✗ App Router: Works with Client Components (React State) [0.22ms]
__tests__/index.test.tsx:
234 | throw error;
235 | }
236 | if (!baseElement) {
237 | // default to document.body instead of documentElement to avoid output of potentially-large
238 | // head elements (such as JSS style blocks) in debug output
239 | baseElement = document.body;
^
ReferenceError: Can't find variable: document
at render (/workspace/with-jest-app/node_modules/@testing-library/react/dist/pure.js:239:19)
at /workspace/with-jest-app/__tests__/index.test.tsx:9:5
✗ Home > renders a heading [0.75ms]
app/utils/add.test.ts:
# Unhandled error between tests
-------------------------------
1 | throw new Error(
^
error: This module cannot be imported from a Client Component module. It should only be used from a Server Component.
at /workspace/with-jest-app/node_modules/server-only/index.js:1:7
-------------------------------
app/blog/[slug]/page.test.tsx:
# Unhandled error between tests
-------------------------------
7 | import 'lodash/isEqualWith.js';
8 | import 'lodash/uniq.js';
9 | import 'css.escape';
10 | import 'aria-query';
11 |
12 | expect.extend(extensions);
^
ReferenceError: Can't find variable: expect
at /workspace/with-jest-app/node_modules/@testing-library/jest-dom/dist/index.mjs:12:1
-------------------------------
0 pass
5 fail
2 errors
Ran 5 tests across 5 files. [284.00ms]
流石にそのままではダメそうですね
エラー内容と、bunがjestからの移行ガイドを出しているので見ながら直していきます
ReferenceError: Can't find variable: document
DOMにアクセスする場合はhappy-dom
の導入が必要みたいなので入れます
bun add -d @happy-dom/global-registrator
touch happydom.ts bunfig.toml
import { GlobalRegistrator } from "@happy-dom/global-registrator";
GlobalRegistrator.register();
[test]
preload = "./happydom.ts"
toHaveTextContent is undefined
一部のマッチャーは使えないのでbunで使えるものに差し替えます
import { render, screen } from "@testing-library/react";
import Page from "./page";
it("App Router: Works with Server Components", () => {
render(<Page />);
// toHaveTextContentを変更
expect(screen.getByRole("heading").textContent).toBe("App Router");
});
他のマッチャーも変えていきます
Found multiple elements with the role "heading"
他テストでrenderされたcomponentがそのまま残り続けているっぽい
もっと良い対応がありそうだが納期がやばいのでgetByTestIdに差し替えて対応する
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h2 data-testid='count'>{count}</h2>
<button type="button" onClick={() => setCount(count + 1)}>
+
</button>
</>
);
}
import { fireEvent, render, screen } from "@testing-library/react";
import Counter from "./counter";
it("App Router: Works with Client Components (React State)", () => {
render(<Counter />);
expect(screen.getByTestId("count").textContent).toBe("0");
fireEvent.click(screen.getByRole("button"));
expect(screen.getByTestId("count").textContent).toBe("1");
});
TypeError: "/vercel.svg" cannot be parsed as a URL.
happy-dom側の問題のようです
対応策を納期内に見つけられなかったため一旦置きにしています
It should only be used from a Server Component
import "server-only";
しているComponentのテストに失敗しています
NextJSの13.4.20
からは成功するようになったよう
こちらもbun testで対応する方法が納期内に見つけられずでした
結果
bun test v1.1.38 (bf2f153f)
app/page.test.tsx:
✓ App Router: Works with Server Components [20.93ms]
app/counter.test.tsx:
✓ App Router: Works with Client Components (React State) [12.84ms]
__tests__/index.test.tsx:
app/utils/add.test.ts:
app/blog/[slug]/page.test.tsx:
✓ App Router: Works with dynamic route segments [1.81ms]
3 pass
0 fail
4 expect() calls
Ran 3 tests across 5 files. [360.00ms]
以上で3/5のテストの移行を行いました
残り2つについてはすぐには対応策を見つけられなかったので空き時間で調べます…
実行時間についてはテストケースが減ったとはいえ3.243 s → 360.00ms
と噂通り爆速でした
既にテストがあるプロダクトではエイヤで移行するのは厳しそうですが、
既存プロダクトに新規でテストを追加したり、新しいプロダクトへの採用であれば良さそうです
気になった点としては基本的な書き方は変わらないものの、現状ではまだ必要な変更点が多い点ですね
特にDOM周りの扱いやNextJSだとAppRouter周りに難がありそうでした
純粋なjs等のテストでは良さそうですがNextJSとかと組み合わせていくとまだまだ課題にぶち当たりそうでした
とはいえ早さは圧倒的なので個人的に小さいものを作ったりする時は積極的に採用していこうかと思います
以上で〆させていただきたいと思います
今回の教訓:締め切り駆動執筆はやめましょう