generated at
fresh-testing-library
概要
Freshアプリケーションのユニットテスト/インテグレーションテスト用のユーティリティです。
リポジトリはここです。

機能
Islandコンポーネント, Handler, Middlewareなどのテストをサポート
Jest互換な expect() APIを提供
Partialsなどの機能のエミュレーションをサポート

インストール
deno.jsonまたはimport_map.jsonに以下を記述する必要があります。
$MODULE_VERSION に指定するURLはhttps://deno.land/x/fresh_testing_libraryから調べられます
json
// ... "imports": { "$std/": "https://deno.land/std@0.190.0/", "🏝️/": "./islands/", "$fresh/": "https://deno.land/x/fresh@1.2.0/", // ... // 以下を追加します (`$MODULE_VERSION`は使用したいバージョンで置き換えます) "$fresh-testing-library": "https://deno.land/x/fresh_testing_library@$MODULE_VERSION/mod.ts", "$fresh-testing-library/": "https://deno.land/x/fresh_testing_library@$MODULE_VERSION/" }, // ...

使い方
Islandコンポーネントのテスト
test/islands/Counter.test.tsx
import { cleanup, render, setup, userEvent, } from "$fresh-testing-library/components.ts"; import { expect } from "$fresh-testing-library/expect.ts"; import { signal } from "@preact/signals"; import { afterEach, beforeAll, describe, it } from "$std/testing/bdd.ts"; import Counter from "🏝️/Counter.tsx"; import { default as manifest } from "./demo/fresh.gen.ts"; describe("islands/Counter.tsx", () => { // partialsのエミュレーションを有効化したい場合は、`setup()`で`manifest`オプションの指定が必要です (不要な場合は指定しなくても問題ありません) beforeAll(() => setup({ manifest })); afterEach(cleanup); it("should work", async () => { const count = signal(9); const user = userEvent.setup(); const screen = render(<Counter count={count} />); const plusOne = screen.getByRole("button", { name: "+1" }); const minusOne = screen.getByRole("button", { name: "-1" }); expect(screen.getByText("9")).toBeInTheDocument(); await user.click(plusOne); expect(screen.queryByText("9")).not.toBeInTheDocument(); expect(screen.getByText("10")).toBeInTheDocument(); await user.click(minusOne); expect(screen.getByText("9")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); }); });

Middlewareのテスト
middleware.test.ts
import { createFreshContext } from "$fresh-testing-library/server.ts"; import { assert } from "$std/assert/assert.ts"; import { assertEquals } from "$std/assert/assert_equals.ts"; import { describe, it } from "$std/testing/bdd.ts"; import { createLoggerMiddleware } from "./demo/routes/(_middlewares)/logger.ts"; import manifest from "./demo/fresh.gen.ts"; describe("createLoggerMiddleware", () => { it("returns a middleware which logs the information about each request", async () => { const messages: Array<string> = []; const testingLogger = { info(...args: Array<unknown>) { messages.push(args.map(String).join("")); }, }; const middleware = createLoggerMiddleware(testingLogger); const path = `/api/users/123`; const req = new Request(`http://localhost:3000${path}`); const ctx = createFreshContext(req, { manifest }); await middleware(req, ctx); assertEquals(messages, [ `<-- GET ${path}`, `--> GET ${path} 200`, ]); }); });

Handlerのテスト
test/routes/api/users/[id].test.ts
import { createFreshContext } from "$fresh-testing-library/server.ts"; import { assert } from "$std/assert/assert.ts"; import { assertEquals } from "$std/assert/assert_equals.ts"; import { describe, it } from "$std/testing/bdd.ts"; import { handler } from "./demo/routes/api/users/[id].ts"; import manifest from "./demo/fresh.gen.ts"; describe("handler.GET", () => { it("should work", async () => { assert(handler.GET); const req = new Request("http://localhost:8000/api/users/1"); const ctx = createFreshContext(req, { manifest }); assertEquals(ctx.params, { id: "1" }); const res = await handler.GET(req, ctx); assertEquals(res.status, 200); assertEquals(await res.text(), "bob"); }); });

Async Route Componentのテスト
createRouteContext() を使うとfresh RouteContext オブジェクトを作成できます。 state オプションを指定すると、 RouteContext.state に対して依存注入ができます。
tests/routes/users/[id].test.tsx
import { cleanup, getByText, render, setup, } from "$fresh-testing-library/components.ts"; import { createFreshContext } from "$fresh-testing-library/server.ts"; import { assertExists } from "$std/assert/assert_exists.ts"; import { afterEach, beforeAll, describe, it } from "$std/testing/bdd.ts"; import { default as UserDetail } from "./demo/routes/users/[id].tsx"; import { default as manifest } from "./demo/fresh.gen.ts"; import { createInMemoryUsers } from "./demo/services/users.ts"; describe("routes/users/[id].tsx", () => { beforeAll(() => setup({ manifest })); afterEach(cleanup); it("should work", async () => { const req = new Request("http://localhost:8000/users/2"); const state = { users: createInMemoryUsers() }; const ctx = createFreshContext<void, typeof state>(req, { // NOTE: If `manifest` option was specified in `setup()`, it can be omitted here. // It is also possible to inject dependencies into `ctx.state` with `state` option. state, }); const screen = render(await UserDetail(req, ctx)); const group = screen.getByRole("group"); assertExists(getByText(group, "bar")); }); });

サブモジュール
typescript
import { createFreshContext } from "$fresh-testing-library/server.ts"; import { cleanup, render, setup, userEvent, } from "$fresh-testing-library/components.ts"; import { expect } from "$fresh-testing-library/expect.ts";

Tips
mswを使う
Deno --location オプションとmswを併用すると、相対URLを指定してfetchを読んでいるコードをテストできます。
mswを使う際は、deno.jsonに以下のように追加しておくと便利です。
deno.json
{ "imports": { // Add the following lines: "msw": "npm:msw@2.0.8", "msw/node": "npm:msw@2.0.8/node" } }
以下のようなイメージで使用できます。
msw.test.ts
import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { expect } from "$fresh-testing-library/expect.ts"; import { afterAll, beforeAll, describe, it } from "$std/testing/bdd.ts"; // `--location`オプションを指定しておくと、`location`が定義されます。 expect(location).toBeTruthy(); describe("msw", () => { const server = setupServer( http.get(`${location.origin}/api/user`, () => HttpResponse.json({ id: 1, name: "foo", })), ); beforeAll(() => server.listen({ onUnhandledRequest: "error" })); afterAll(() => server.close()); it("can be used to intercept requests", async () => { const res = await fetch("/api/user"); expect(await res.json()).toEqual({ id: 1, name: "foo" }); }); });

補足
deno-puppeteerやcreateHandler()との棲み分けについて
基本的に、fresh-testing-librarydeno-puppeteerfresh createHandler() などを置き換えることは想定していません。
用途に応じて使い分けることを想定しています。
fresh-testing-libraryは基本的にユニットテスト/インテグレーションテストの記述を想定しています。
Islandコンポーネントをその他の要素とは独立してテストする
ctx.state への依存注入などにより、 Handler / Middleware などをその他の要素とは独立してテストする
逆に、E2Eテストなどを記述したいケースではdeno-puppeteer createHandler() などを使うことを推奨します。

TODO/やりたいこと
Fresh組み込みコンポーネントや機能のサポートの拡充
✅Partialsのサポート (v0.13.0)
App/Layoutのサポート
vitest-preview / jest-preview 相当の仕組み
これは別のライブラリとして分離した方がよいのかもしれない

リンク