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",
+ },
};