Bun 付属の Jest 互換テストランナーを使ってみる
こんにちは、Gaji-Labo フロントエンドエンジニアの石垣です。
今回は、前回の自分の記事でご紹介した JavaScript 用のオールインワンツールキット「Bun」に付属しているテストランナーを使ってみたのでその感触をお伝えします。
Bun のテストランナーについて
Bun にはランタイムやパッケージマネージャーの他にテストランナーが付属しています。これは Jest と互換性があり、基本的には Jest と同じように使えます。
ただし未対応の matcher もあるようです。公式ドキュメントに対応表がありますので、詳細はそちらを参照ください。
Bun のテストランナーは次の機能をサポートしているようです。
- TypeScript と JSX
- ライフサイクルフック
- スナップショットテスト
- UIとDOMのテスト
- ウォッチモード
- スクリプトのプリロード
今回は簡単な React コンポーネントのテストを書いてみます。
テストを書いてみる
テスト対象は、前回の記事で react-app テンプレートを用いて生成したプロジェクトの src/App.tsx です。
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
これには画像、テキスト、リンクが含まれています。
まずはスナップショットを撮るだけのテストを書いてみます。
import renderer from "react-test-renderer";
import { test, expect } from "bun:test";
import App from "./App";
test("render a component", () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
renderer に react-test-renderer
を使っています。
二行目の test
expect
を Bun のものに置き換えています。このような単純なテストであれば Jest から import を差し替えるだけで動作します。
bun test
を叩くと自動的にプロジェクト内のテストを探して実行してくれます。
実行結果は以下のように表示されます。
✓ render a component [9.74ms]
1 pass
0 fail
snapshots: +1 added
1 expect() calls
Ran 1 tests across 1 files.
続いて、コンポーネント内の要素を検証するテストを書いてみます。
import { render, screen } from "@testing-library/react";
import { expect, it, describe } from "bun:test";
import App from "./App";
describe("render a component", () => {
render(<App />);
it("exists img element has className='App-logo' and alt='logo'", () => {
const logoElement = screen.getByRole("img", { name: /logo/i });
expect(logoElement).not.toBeNull();
expect(logoElement.className).toBe("App-logo");
expect(logoElement.alt).toBe("logo");
});
it("exists link element has href='https://reactjs.org/'", () => {
const linkElement = screen.getByRole("link", { name: /learn react/i });
expect(linkElement).not.toBeNull();
expect(linkElement.href).toBe("https://reactjs.org/");
});
it("if exists link element has target='_blank' then it has rel='noopener noreferrer'", () => {
const blankLinkElement = screen.getByRole("link", { target: "_blank" });
expect(blankLinkElement).not.toBeNull();
expect(blankLinkElement.rel).toBe("noopener noreferrer");
});
});
こちらも Jest と同じように書いていますが、 toBeInTheDocument 等の @testing-library/jest-dom
の matcher は未対応のようですので別の方法で書いています。
ref: https://github.com/oven-sh/bun/issues/1825#issuecomment-1673696251
また、このように DOM にアクセスする必要があるテストは前もって happy-dom
を導入する必要があります。
詳細は公式ドキュメントを参照ください。
実行結果は以下のように表示されます。
✓ render a component > exists img element has className='App-logo' and alt='logo' [13.53ms]
✓ render a component > exists link element has href='https://reactjs.org/' [1.56ms]
✓ render a component > if exists link element has target='_blank' then it has rel='noopener noreferrer' [0.36ms]
3 pass
0 fail
7 expect() calls
Ran 3 tests across 1 files. [436.00ms]
同じテストを Jest で実行してみます。
PASS src/App.test.js
render a component
✓ exists img element has className='App-logo' and alt='logo' (80 ms)
✓ exists link element has href='https://reactjs.org/' (32 ms)
✓ if exists link element has target='_blank' then it has rel='noopener noreferrer' (10 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 1.155 s
Ran all test suites related to changed files.
Bun に比べトータルで約2倍の実行速度がかかっています。
要素を検証するだけのテストでも2倍の差が出ているので、大規模なテストの場合はかなり有意な差が出るかもしれません。
ただ、テストが落ちたときの該当箇所やエラーメッセージは Jest の方がわかりやすいように感じました。この点は今後の改善に期待したいです。
おわりに
今回は Bun のテストランナーを使ってみました。
Jest と互換性があるので、Jest からの移行も比較的容易にできそうです。
速度的には単純なテストでも速度差が出ているのでかなりのメリットだと思います。
引き続き Bun の機能を試していきたいと思います。
Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります
フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。
「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。
オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。
Next.js, React, TypeScript の相談をする!