以下のコードは、Deno標準ライブラリを使って書いた単純なHello Worldサーバーです。
import { serve } from "https://deno.land/std@0.117.0/http/server.ts";
console.log("listening on http://localhost:8000/");
serve(() => new Response("Hello World\n"));
Denoには組み込みのテストツールが備わっており、test.ts
を作成してdeno test
コマンドを打つだけでテストを行うことができます。
import "./serve.ts";
Deno.test("server test", ()=>{
// ???
})
さて、先ほどのHello Worldサーバーをテストするには、どのようなコードを書けばいいのでしょうか?
AbortController について
まず、テストが終了した後にサーバーを止める手段が必要です。
サーバーを停止させるには、Web標準のAPIであるAbortController
を使用します。
AbortController
をserve()
関数の第2引数に渡してあげることで、外部からサーバーを停止させることができます。
更に、test.ts
からサーバーを起動・停止を管理するために、全体を関数で囲ってexport
します。
import { serve } from "https://deno.land/std@0.117.0/http/server.ts";
console.log("listening on http://localhost:8000/");
// export for test
export function startServer() {
const controller = new AbortController(); // サーバーを停止させるためのAbortControllerを生成
const server = serve(() => new Response("Hello World\n"), controller);
return {
server, // サーバーが終了した時に解決されるPromise
controller, // controller.abort()を呼ぶとサーバーが停止する
};
}
test.ts
では、startServer
関数をimportして使います。
import { assertEquals } from "https://deno.land/std@0.117.0/testing/asserts.ts";
import { startServer } from "./serve.ts";
Deno.test({
name: "server test",
async fn() {
// サーバー起動
const { server, controller } = startServer();
try {
// fetch()を使ってlocalhostへアクセスし、正しいレスポンスが返ってくるか確認
const response = await fetch("http://localhost:8000/");
assertEquals(await response.text(), "Hello World\n");
} finally {
// サーバー停止
controller.abort();
await server; // サーバーの終了を待つ
}
},
});
ここで、最後の行でawait server
しているのに注意してください。
このserver
という変数の中には、「サーバーが終了したときに解決されるPromise」が入っています。このPromiseが解決されるのを待たないと、deno testコマンドで実行した時にエラーが出る可能性があります。
import.meta.main について
上のコードではまだ不完全で、serve.ts
を実行してもstartServer()
が呼ばれないため、サーバーが起動しません。そのため、
-
serve.ts
から実行された時はstartServer()
関数を実行する -
test.ts
から実行された時はstartServer()
関数を実行しない
という風にしなくてはいけません。ここでimport.meta.main
の出番です。
import.meta.main
はDeno固有のプロパティで、「自身のモジュールがメインモジュールかどうか」を返します。
メインモジュールならばtrue
、他のファイルからimportされた時はfalse
になります。
import.meta.main
を使ってserve.ts
を書きなおすと、以下のようになります。
import { serve } from "https://deno.land/std@0.117.0/http/server.ts";
console.log("listening on http://localhost:8000/");
// export for test
export function startServer() {
const controller = new AbortController();
const server = serve(() => new Response("Hello World\n"), controller);
return {
server, // サーバーが終了した時に解決されるPromise
controller, // controller.abort()を呼ぶとサーバーが停止する
};
}
if (import.meta.main) {
// メインモジュールの時のみサーバーを起動
startServer();
}
以上で、標準ライブラリのサーバーをテストすることができました。以下のコマンドで実行とテストを行うことができます。
> deno run --allow-net=0.0.0.0:8000 ./serve.ts
listening on http://localhost:8000/
> deno test --allow-net=0.0.0.0:8000,localhost:8000 ./test_test.ts
listening on http://localhost:8000/
running 1 test from file:///C:/Users/azusa/work/deno/test/test_test.ts
test server test ... ok (360ms)
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (395ms)
まとめ
標準ライブラリのサーバーをテストするには、AbortController
とimport.meta.main
を使います。
おまけ
複数のURLに対するテストを行うには、サーバーの起動・停止部分を関数に括り出しておくと便利です。
import { assertEquals } from "https://deno.land/std@0.117.0/testing/asserts.ts";
import { startServer } from "./serve.ts";
async function testServer(
path: string, // テスト対象のパス
fn: (res: Response) => void | Promise<void>, // テスト内容を指定する関数
) {
// サーバー起動
const { server, controller } = startServer();
try {
const response = await fetch(new URL(path, "http://localhost:8000/"));
await fn(response);
} finally {
// サーバー停止
controller.abort();
await server;
}
}
Deno.test({
name: "server test path1",
async fn() {
await testServer("/path1", async (response) => {
// `/path1`にアクセスした時のレスポンスが`response`変数に入っている
assertEquals(await response.text(), "Hello World\n");
});
},
});
Deno.test({
name: "server test path2",
async fn() {
await testServer("/path2", async (response) => {
assertEquals(await response.text(), "Hello World\n");
});
},
});