fetch をモックしたい

Deno でテストを書き進めるにあたり、fetch の実行結果をモックしたくなりました。
トライしてみたが上手くいかず、結果公開されているモジュールで目的は果たせたのでまとめです。

参考

トライ

グローバル空間にある fetch を Promise<Response> を返す関数に差し替えればできるだろうと考えました。
以下実装例。

mod.ts
1
2
3
export async function externalCall(): Promise<Response>{
return await fetch("https://ccbaxy.xyz/blog/")
}
fetch_mock.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
let org: Function | null = null;

function setFetchMock(responseBody: any) {
org = globalThis.fetch;
let body = responseBody;

if (typeof responseBody === "number") {
body = `${responseBody}`;
}

if (typeof responseBody === "object") {
body = JSON.stringify(responseBody);
}

globalThis.fetch = (url: string) => {
return new Response(body);
};
}

function resetFetchMock() {
globalThis.fetch = org;
}

export { resetFetchMock, setFetchMock };
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
25
26
27
28
29
30
31
32
33
import { assertEquals } from "https://deno.land/std@0.127.0/testing/asserts.ts";
import { externalCall } from "./mod.ts";
import { resetFetchMock, setFetchMock } from "./fetch_mock.ts";

Deno.test("test #1", async () => {
setFetchMock("AAA");

const res = await externalCall();
const text = await res.text();

assertEquals(text, "AAA");
resetFetchMock();
});

Deno.test("test #2", async () => {
setFetchMock(1);

const res = await externalCall();
const text = await res.json();

assertEquals(text, 1);
resetFetchMock();
});

Deno.test("test #3", async () => {
setFetchMock({ a: 1 });

const res = await externalCall();
const text = await res.json();

assertEquals(text, { a: 1 });
resetFetchMock();
});

外部のURL https://ccbaxy.xyz/blog/ に対してアクセスして、setFetchMock で設定した値を fetchが返すようになりました。
ただ、deno test test.ts をやるとTS2322 [ERROR] となり、実行には、--no-check が必要でした。
もしくは、fetch_mock.tsfetch_mock.js として準備すれば不要ではあります。
ここまでやってみて公開されているモジュールを探し始めました。

公開されてるモジュールで十分だった

というわけで探してみるとmock_fetch を見つけました。
これが便利でした。

test2.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
import { assertEquals } from "https://deno.land/std@0.127.0/testing/asserts.ts";
import { externalCall } from "./mod.ts";
import * as mf from "https://deno.land/x/mock_fetch@0.3.0/mod.ts";

Deno.test("test #1", async () => {
mf.install();
mf.mock("GET@/blog/", (_req, params) => {
return new Response("externalCallA", {
status: 200,
});
});

const res = await externalCall();
const text = await res.text();

assertEquals(text, "externalCallA");
mf.uninstall();
});

Deno.test("test #2", async () => {
mf.install();
mf.mock("GET@/blog/", (_req, params) => {
return new Response((1).toString(), {
status: 200,
});
});

const res = await externalCall();
const text = await res.json();

assertEquals(text, 1);
mf.uninstall();
});

Deno.test("test #3", async () => {
mf.install();
mf.mock("GET@/blog/", (_req, params) => {
return new Response(JSON.stringify({ a: 1 }), {
status: 200,
});
});

const res = await externalCall();
const text = await res.json();

assertEquals(text, { a: 1 });
mf.uninstall();
});

これなら、deno test test2.ts で実行できました。


さっと mock_fetch を見てみると、typeof globalThis.fetch; といった型定義があるので、このあたりで型チェックを回避できていそう。

ではでは。