先日 https://deno.land/x を見ていたら、SuperOak というモジュールを見つけた。
名前だけ見た時は、「誰かが oak を魔改造してそういう名前でも付けたか?」と中身を見ると、oak の HTTP アサーションをするモジュールだった。
スーパーバイザー的な意味での super~~であったらしい?
(2022/07/18 19:45追記: この名前の経緯について、Twitterで教えてもらった。 visionmedia/superagent の登場以降、HTTPテストライブラリには super~~ と付けるのが、慣習になっているんだそう。 )
というわけで、見つけた super~~ なモジュールを試してみたい。
参考
superdeno 話の発端は SuperOak であったのだが、これは SuperDeno に依存している。 というところで、SuperDeno から試していく。
サンプルを見ると、opine を使ったことは無いので std/http で確認していく。
一番最初に書くような std/http のサーバーは、サンプル通り次のようになります。
app.ts 1 2 3 import { serve } from "https://deno.land/std@$STD_VERSION/http/server.ts" ;serve (() => new Response ("Hello World\n" ), { port : 8080 });
これを SuperDeno で HTTP アサーションをするには、サーバーを起動するのではなく、Server クラスのインスタンスが欲しいので、次のようにできる。
app.ts 1 2 3 4 5 6 import { Server } from "https://deno.land/std@0.148.0/http/server.ts" ;export const server = new Server ({ handler : () => new Response ("Hello World" ), port : 8080 , });
app.ts 1 2 3 import { server } from "./server.ts" ;server.listenAndServe ();
app_test.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 import { server } from "./server.ts" ;import { superdeno } from "https://deno.land/x/superdeno/mod.ts" ;import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts" ;Deno .test ("HTTP assert" , async () => { const r = await superdeno(server) .get ("/" ) .expect ("Content-Type" , /text/ ) .expect ("Content-Length" , "11" ) .expect (200 ); assertEquals (r.text , "Hello World" ); });
実行すると次のようになります。
1 2 3 4 5 6 7 8 9 $ deno run --allow-net=0.0.0.0:8080 app.ts $ deno test --allow-net app_test.ts running 1 test from ./app_test.ts HTTP assert ... ok (40ms) ok | 1 passed | 0 failed (68ms)
どうやら、テストはできているらしいが本当かわからないので、ちょっと書き換えて実行してみる。
app_test.ts(ワザと失敗するテスト) 1 2 3 4 5 6 7 8 9 10 11 12 13 import { server } from "./server.ts" ;import { superdeno } from "https://deno.land/x/superdeno/mod.ts" ;import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts" ;Deno .test ("HTTP assert" , async () => { const r = await superdeno(server) .get ("/" ) .expect ("Content-Type" , /text/ ) .expect ("Content-Length" , "11" ) .expect (201 ); assertEquals (r.text , "Hello World" ); });
改めて実行すると次のように。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ deno test --allow-net app_test.ts running 1 test from ./app_test.ts HTTP assert ... FAILED (41ms) ERRORS HTTP assert => ./app_test.ts:5:6 error: Error: expected 201 "Created" , got 200 "OK" return new Error(`expected ${status} "${a} " , got ${res.status} "${b} " `); ^ at Test. at Test. at Test. at https://deno.land/x/superdeno@4.8.0/src/test.ts:479:23 at async close (https://deno.land/x/superdeno@4.8.0/src/close.ts:47:46) FAILURES HTTP assert => ./app_test.ts:5:6 FAILED | 0 passed | 1 failed (69ms) error: Test failed
どうやらちゃんと動いていそうです。
superdeno もう少し掘る サーバー側で、ヘッダーへの情報の付与や、ペイロードの付与を確認してみます。
ヘッダーの付与 サーバー側。
server.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 import { Server } from "https://deno.land/std@0.148.0/http/server.ts" ;export function getServer ( ) { return new Server ({ handler : (request ) => { if (request.headers .get ("accept" ) !== "application/json" ) { return new Response ("" , { status : 400 }); } return Response .json ({ text : "Hello World" }); }, port : 8080 , }); }
テスト側。
app_test.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { getServer } from "./server.ts" ;import { superdeno } from "https://deno.land/x/superdeno/mod.ts" ;import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts" ;Deno .test ("HTTP assert" , async (t) => { await t.step ("#1" , async () => { const r = await superdeno(getServer ()) .get ("/" ) .set ("Accept" , "application/json" ) .expect ("Content-Type" , /json/ ) .expect (200 ); const body = r.body ; assertEquals (body.text , "Hello World" ); }); await t.step ("#2" , async () => { await superdeno(getServer ()) .get ("/" ) .expect (400 ); }); });
このようにすることで、テストができる。 先の実装と比較して、new Server
がテストのステップの都度行われるようにした。
なぜかというと、SuperDeno の引数に与えたサーバーが、1 回の実行によりクローズしてしまったため。 この対策を取らないと 2 つめにテストが行われたものは、確実に以下のエラーになっていた。
1 2 3 4 5 6 7 8 9 10 11 SuperDeno experienced an unexpected server error. Http: Server closed at Server.listenAndServe (https://deno.land/std@0.148.0/http/server.ts:178:13) at new Test (https://deno.land/x/superdeno@4.8.0/src/test.ts:227:43) at Object.obj.<computed> [as get] (https://deno.land/x/superdeno@4.8.0/src/superdeno.ts:101:14) at file:///usr/src/app/app_test.ts:30:8 at testStepSanitizer (deno:runtime/js/40_testing.js:449:13) at asyncOpSanitizer (deno:runtime/js/40_testing.js:147:15) at resourceSanitizer (deno:runtime/js/40_testing.js:375:13) at exitSanitizer (deno:runtime/js/40_testing.js:432:15) at TestContext.step (deno:runtime/js/40_testing.js:1415:19) at file:///usr/src/app/app_test.ts:28:11
ペイロードの付与(json) ここまで何もリクエストに付与しないで来たので、リクエストに json をつけてリクエストを試みます。
この辺りは、SuperOak にサンプルが有るくらいだったりしていて、SuperDeno には、説明が有りません。
server.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { Server } from "https://deno.land/std@0.148.0/http/server.ts" ;export function getServer ( ) { return new Server ({ handler : async (request) => { if (request.headers .get ("accept" ) !== "application/json" ) { return new Response ("" , { status : 400 }); } console .log (request.method ); if (request.method !== "POST" ) { return new Response ("" , { status : 400 }); } const json = await request.json (); return Response .json ({ text : json.text }); }, port : 8080 , }); }
app_test.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { getServer } from "./server.ts" ;import { superdeno } from "https://deno.land/x/superdeno/mod.ts" ;import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts" ;Deno .test ("HTTP assert" , async () => { const r = await superdeno(getServer ()) .post ("/" ) .set ("Accept" , "application/json" ) .send ({ text : "Hello Deno!" }) .expect ("Content-Type" , /json/ ) .expect (200 ); const body = r.body ; assertEquals (body.text , "Hello Deno!" ); });
ペイロードの付与(Form) json が送れたで Form も送ってみます。
server.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { Server } from "https://deno.land/std@0.148.0/http/server.ts" ;export function getServer ( ) { return new Server ({ handler : async (request) => { console .log (request.method ); if (request.method !== "POST" ) { return new Response ("" , { status : 400 }); } const form = await request.formData (); if (form.get ("text" ) !== "Deno!" ) { return new Response ("" , { status : 400 }); } return new Response ("" , { status : 200 }); }, port : 8080 , }); }
app_test.ts 1 2 3 4 5 6 7 8 9 10 11 import { getServer } from "./server.ts" ;import { superdeno } from "https://deno.land/x/superdeno/mod.ts" ;import { assertEquals } from "https://deno.land/std@0.148.0/testing/asserts.ts" ;Deno .test ("HTTP assert" , async () => { await superdeno(getServer ()) .post ("/" ) .set ("Content-Type" , "application/x-www-form-urlencoded" ) .send ("text=Deno!" ) .expect (200 ); });
フォームの送信もテストできました。
superoak superoak は、superdeno と同一作者のモジュールです。
これまでのものを踏まえて GET, POST(json), POST(From) を作ってみます。
server.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import { Application , type Context , Router , } from "https://deno.land/x/oak@v10.4.0/mod.ts" ; const router = new Router ();router.get ("/" , ({ response }: Context ) => { response.body = "Hello world" ; }); router.post ("/json" , async ({ request, response }: Context ) => { if (request.headers .get ("accept" ) !== "application/json" ) { return new Response ("" , { status : 400 }); } const json = await request.body ().value ; if (!json.text ) { response.status = 400 ; return ; } response.body = { text : json.text }; }); router.post ("/form" , async ({ request, response }: Context ) => { if (request.method !== "POST" ) { return new Response ("" , { status : 400 }); } const form = await request.body ({ type : "form" }).value ; if (form.get ("text" ) !== "Deno!" ) { return new Response ("" , { status : 400 }); } response.status = 200 ; }); router.get ("/form" , async ({ request, response }: Context ) => { response.body = ` <html> <body> <form METHOD="POST"> <input name="text"> <button type="submit">submit</button> </form> </body> </html> ` ;}); const app = new Application ();app.use (router.routes ()); app.use (router.allowedMethods ()); export { app };
–
というところで super~~ なモジュールを調べてみました。
oak のテストを書くとき、リクエストの内容と応答のテストをしたいと思っていたので、ドンピシャの内容でした。 ページの中からトークンを取得して、それを含めてリクエスト。なんてのを試したいので、もう少し深堀をしていくと思います。 (そこまでやるなら別のツールを使うのが筋とも感じるところもありますが) (更新(2022/07/20) SuparOak でフォームと Cookie を送ってみる )
他に、deno-libs/superfetch というものも見つけて試してみましたが、こちらはサンプルの通り node 互換のもののようなので、見送りをしました。
ではでは。