Hono on Cloudflare Workers のテストを書くときにハマったこと
Hono on Cloudflare Workers を使って実装した Web アプリケーションのテストを書いたとき、いくつかハマったことがあったのでその解決策について記載する。
Hono は jest-environment-miniflare
を使ったテストを推奨しているため、Miniflare による仮想環境と、テストフレームワークである Jest を利用した。
- Miniflare:Cloudflare Workers を開発/テストするためのシミュレーター
- Jest:テストフレームワーク
確認した環境
- wrangler v2.15.1
- Hono v3.1.5
- jest v29.5.0
HTTP レスポンスのモックには nock が使えない
発生したこと
外部へのリクエスト結果をモックするため、次のように nock を利用しようとした。
しかし、モックされずに、https://example.com
へリクエストが送られてしまった。
// 以下は example.com へのリクエストレスポンスが mock されない
describe("GET /get", () => {
beforeAll(() => {
nock("https://example.com").get('/404').reply(404);
});
it("should be 404", async () => {
const res = await app.request(`https://localhost/get?url=https://example.com/404`);
expect(res.status).toBe(404);
});
});
原因
nock は http
、https
および XMLHttpRequest
のモジュールを使った fetch リクエストをモックするライブラリである。
一方で、Miniflare v2 以降の fetch の実装には、undici という HTTP クライアントを利用している。
そのため、HTTP リクエストレスポンスがモックされなかった。
解決策
HTTP レスポンスをモックするには、jest-environment-miniflare
の getMiniflareFetchMock()
を使う。
// getMiniflareFetchMock() を使っているため example.com へのレスポンスが mock される
describe("GET /ics", () => {
beforeAll(() => {
const fetchMock = getMiniflareFetchMock();
fetchMock.disableNetConnect();
const origin = fetchMock.get("https://example.com");
origin.intercept({ method: "GET", path: "/404" }).reply(404, "not found");
});
it("should be 404", async () => {
const res = await app.request(`https://localhost/get?url=https://example.com/404`);
expect(res.status).toBe(404);
});
});
getMiniflareFetchMock()
を型解決するためには、tsconfig.json
に以下を追記する。
"types": [
"@cloudflare/workers-types",
"@types/jest",
+ "jest-environment-miniflare/globals"
],
__STATIC_CONTENT_MANIFEST モジュールがないというエラーが発生する
Hono では、ソースコードディレクトリ以外に置いた静的ファイルを配信するためのアダプターがある。
アダプターを利用するには、GET メソッドの URL パスに、serveStatic()
を渡す。
import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
// 略
const app = new Hono();
app.get("/static/*", serveStatic({ root: "./" }));
静的ファイルを置いたディレクトリは、wrangler.toml
で指定する。
name = ""
compatibility_date = "2023-01-01"
+ [site]
+ bucket = "./public"
このように記載すると、public/static
以下に置いたファイルは /static/
以下のパスで配信される。
たとえば、次のようにおいた public/static/foo.png
は /static/foo.png
で配信される。
.
├── README.md
├── // 略
├── public
│ └── static
│ └── foo.png
├── src
│ ├── index.test.tsx
│ └── index.tsx
├── tsconfig.json
└── wrangler.toml
発生したこと
テストを実行すると、__STATIC_CONTENT_MANIFEST
のモジュール解決に失敗したというエラーが発生した。
$ npx jest
FAIL src/index.test.tsx
● Test suite failed to run
Cannot find module '__STATIC_CONTENT_MANIFEST' from 'node_modules/hono/dist/cjs/adapter/cloudflare-workers/server-static-module.js'
Require stack:
node_modules/hono/dist/cjs/adapter/cloudflare-workers/server-static-module.js
node_modules/hono/dist/cjs/adapter/cloudflare-workers/index.js
src/index.tsx
src/index.test.tsx
原因
miniflare では、静的ファイルの情報は Workers KV へ格納されるため、その型情報は __STATIC_CONTENT_MANIFEST
に保存される。
しかし、jest-environment-miniflare
で動かすときには __STATIC_CONTENT_MANIFEST
が存在せず、モジュールを解決できない。
解決策
静的ファイルの情報を作成するため、__STATIC_CONTENT_MANIFEST
のスタブを作成する。
/**
* __STATIC_CONTENT_MANIFEST stub
*/
export default "{}";
module.exports = {
testMatch: ["**/*.test.(ts|tsx)"],
// 略
testEnvironment: "miniflare",
+ moduleNameMapper: {
+ __STATIC_CONTENT_MANIFEST: "<rootDir>/src/manifest.ts",
+ },
};