- 「モック化戦略」ってそもそも何の話?
- まずは「何をモックするのか」をはっきりさせる
- 戦略1:依存を「引数として渡す」モック化
- 戦略2:「インターフェース」を決めてモックを差し替える
- 戦略3:時間をモックする(タイマー・遅延のテスト)
- 戦略4:戻り値を「テストしやすい形」にする
- 戦略5:モックの「粒度」を意識する
- 戦略6:モックを「使い捨て」ではなく「設計の一部」として考える
- 初心者として「モック化戦略」で本当に意識してほしいこと
- まずは「何をモックするのか」をはっきりさせる
- 戦略1:依存を「引数として渡す」モック化
- 戦略2:「インターフェース」を決めてモックを差し替える
- 戦略3:時間をモックする(タイマー・遅延のテスト)
- 戦略4:戻り値を「テストしやすい形」にする
- 戦略5:モックの「粒度」を意識する
- 戦略6:モックを「使い捨て」ではなく「設計の一部」として考える
- 初心者として「モック化戦略」で本当に意識してほしいこと
「モック化戦略」ってそもそも何の話?
非同期処理をテストしようとすると、必ずぶつかる壁があります。
それが「外部とのやり取り」です。
サーバーへのリクエスト
データベース
タイマー(setTimeout)
ブラウザの API(localStorage, IndexedDB など)
これらを本物のままテストで使うと、
テストが遅い
テストが不安定(ネットワーク次第)
再現性が低い(たまたま通る・たまたま落ちる)
という地獄になります。
そこで出てくるのが モック(mock) です。
「本物の代わりに、テスト用のニセモノを使う」戦略のことです。
ここでは、
非同期処理に特化して「どうモックを設計するか」を、
初心者向けにかみ砕いて話していきます。
まずは「何をモックするのか」をはっきりさせる
非同期でよくモック対象になるもの
非同期処理でモックしたくなるものは、だいたい決まっています。
ネットワーク(fetch, axios など)
時間(setTimeout, setInterval, Date.now)
ストレージ(localStorage, IndexedDB)
外部サービス SDK(Firebase, Stripe など)
ポイントは、
「テストを遅く・不安定にするもの」
「テスト環境に存在しないもの」
をモックの候補として見ることです。
そしてもう一つ大事なのが、
「モックしやすいようにコードを設計する」
という発想です。
モックは「テストのための裏技」ではなく、
設計の一部として最初から考えた方が、結果的に楽になります。
戦略1:依存を「引数として渡す」モック化
fetch をモックしやすい形にする
まずは一番シンプルで強力な戦略から。
「外部依存を引数として受け取る」スタイルです。
悪い例から見てみます。
async function fetchCurrentUser() {
const res = await fetch("/api/user");
if (!res.ok) {
throw new Error("ユーザー取得に失敗しました");
}
return res.json();
}
JavaScriptこの関数をテストしようとすると、
グローバルの fetch を上書きしたり、テストフレームワークの機能に頼ったりする必要が出てきます。
これを「モックしやすい形」に変えると、こうなります。
async function fetchCurrentUser(fetchImpl) {
const res = await fetchImpl("/api/user");
if (!res.ok) {
throw new Error("ユーザー取得に失敗しました");
}
return res.json();
}
JavaScript本番コードではこう呼びます。
const user = await fetchCurrentUser(fetch);
JavaScriptテストでは、fetchImpl にモックを渡せます。
async function fakeFetch() {
return {
ok: true,
json: async () => ({ id: 1, name: "テスト太郎" }),
};
}
const user = await fetchCurrentUser(fakeFetch);
// user.id === 1, user.name === "テスト太郎" をテストできる
JavaScriptここでの重要ポイントは、
「モックしやすさは、テストコード側ではなく“プロダクションコードの引数設計”で決まる」
ということです。
「あとからモックしたい」ではなく、
「最初からモックできる形にしておく」が正解に近いです。
戦略2:「インターフェース」を決めてモックを差し替える
fetch 以外にも応用できる形にする
さっきの fetchImpl は、実は「インターフェース」を決めているとも言えます。
「URL を受け取って、{ ok, json } を返す関数」
という約束を作っているわけです。
この「インターフェース」を意識すると、
他の非同期依存にも同じパターンを使えます。
例えば、ユーザーリポジトリを考えます。
async function getUserById(userRepository, id) {
return userRepository.findById(id);
}
JavaScript本番では、こういう実装を渡します。
const userRepository = {
async findById(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("ユーザー取得に失敗しました");
return res.json();
},
};
const user = await getUserById(userRepository, 1);
JavaScriptテストでは、こういうモックを渡します。
const fakeUserRepository = {
async findById(id) {
return { id, name: "モックユーザー" };
},
};
const user = await getUserById(fakeUserRepository, 1);
// user.name が "モックユーザー" であることをテスト
JavaScriptここでの戦略は、
「非同期依存を“オブジェクトのメソッド”としてまとめ、そのインターフェースをモックで再現する」
というものです。
関数一個ではなく、「役割ごとのオブジェクト」を渡すと、
テスト時に差し替えやすくなります。
戦略3:時間をモックする(タイマー・遅延のテスト)
setTimeout を直接使わない形にする
時間に依存する非同期処理も、モック化戦略がないとテストがつらくなります。
例えば、こういうコード。
function showMessageWithDelay(message) {
setTimeout(() => {
console.log(message);
}, 1000);
}
JavaScriptこれをテストしようとすると、
「1秒待つ」か、「テストフレームワークのタイマー機能に依存する」必要が出てきます。
ここでも同じ発想で、
「時間の仕組み」を注入できるようにします。
function showMessageWithDelay(message, { setTimeoutImpl }) {
setTimeoutImpl(() => {
console.log(message);
}, 1000);
}
JavaScript本番ではこう呼びます。
showMessageWithDelay("hello", { setTimeoutImpl: setTimeout });
JavaScriptテストでは、こう書けます。
const calls = [];
function fakeSetTimeout(fn, ms) {
calls.push({ fn, ms });
}
showMessageWithDelay("hello", { setTimeoutImpl: fakeSetTimeout });
// calls[0].ms が 1000 かどうかをテスト
// calls[0].fn() を自分で呼んで、console.log の結果を確認する
JavaScriptここでの重要ポイントは、
「時間に依存する非同期処理も、“時間 API” をモック可能な形にしておく」
ということです。
setTimeout を直接呼ぶのではなく、
「setTimeout 的なもの」を引数で受け取る設計にしておくと、
テストが一気に楽になります。
戦略4:戻り値を「テストしやすい形」にする
副作用だけの非同期関数はモック地獄になりやすい
例えば、こういう関数。
async function saveSettings(settings) {
const res = await fetch("/api/settings", {
method: "POST",
body: JSON.stringify(settings),
});
if (!res.ok) {
alert("保存に失敗しました");
return;
}
alert("保存しました");
}
JavaScriptこれをテストしようとすると、
fetch をモックする
alert をモックする
など、モックだらけになります。
ここでの戦略は、
「非同期関数に“意味のある戻り値”を持たせる」
ことです。
例えば、こう変えます。
async function postSettings(fetchImpl, settings) {
const res = await fetchImpl("/api/settings", {
method: "POST",
body: JSON.stringify(settings),
});
if (!res.ok) {
return { ok: false, error: new Error("保存に失敗しました") };
}
return { ok: true };
}
JavaScriptテストでは、こう書けます。
async function fakeFetchOk() {
return { ok: true };
}
async function fakeFetchFail() {
return { ok: false };
}
const successResult = await postSettings(fakeFetchOk, { theme: "dark" });
// successResult.ok === true
const failResult = await postSettings(fakeFetchFail, { theme: "dark" });
// failResult.ok === false, failResult.error.message をテスト
JavaScriptUI 側では、こう使います。
async function saveSettingsWithUI(settings) {
const result = await postSettings(fetch, settings);
if (!result.ok) {
alert(result.error.message);
} else {
alert("保存しました");
}
}
JavaScriptここでのポイントは、
「モックする対象を減らすために、“副作用を外に追い出し、非同期関数は結果だけ返すようにする”」
という発想です。
戦略5:モックの「粒度」を意識する
どこまでモックするかを決める
モック化戦略でよくハマるのが、
「モックしすぎて何をテストしているのか分からなくなる」状態です。
例えば、こんな構成を考えます。
fetch をモック
その上のリポジトリをモック
さらにその上のサービスをモック
こうなると、
「結局、本物はどこにもいない」状態になり、
テストの意味が薄くなります。
ここで大事なのは、
「どのレイヤーを“本物”としてテストし、どのレイヤーをモックにするか」
を意識して決めることです。
例えば、
リポジトリのテスト → fetch をモックして、リポジトリのロジックを本物でテスト
サービスのテスト → リポジトリをモックして、サービスのロジックを本物でテスト
UI のテスト → サービスをモックして、UI の振る舞いをテスト
というように、
「一段下のレイヤーだけをモックする」
というルールを決めると、テストの意味がはっきりします。
戦略6:モックを「使い捨て」ではなく「設計の一部」として考える
モックも「API の一種」として設計する
雑にモックを書くと、こうなりがちです。
const fakeFetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({ id: 1 }),
});
JavaScriptこれでも動きますが、
テストファイルごとにバラバラなモックが増えていきます。
もう一歩進めて、
「モックも一つの実装」として設計してしまうのもアリです。
function createFakeFetchUser({ shouldFail = false } = {}) {
return async function fakeFetch(url) {
if (!url.startsWith("/api/user")) {
throw new Error("想定外の URL");
}
if (shouldFail) {
return { ok: false };
}
return {
ok: true,
json: async () => ({ id: 1, name: "モックユーザー" }),
};
};
}
JavaScriptテストでは、こう使えます。
const fetchOk = createFakeFetchUser();
const fetchFail = createFakeFetchUser({ shouldFail: true });
JavaScriptここでのポイントは、
「モックも“ちゃんとしたコード”として設計しておくと、再利用できるし、テストの意図も読みやすくなる」
ということです。
モックを「その場しのぎのニセモノ」ではなく、
「テスト用のもう一つの実装」として扱うイメージです。
初心者として「モック化戦略」で本当に意識してほしいこと
最後に、あなたの頭の中に置いておいてほしい問いをまとめます。
この非同期関数は、何と何に依存しているか?(fetch, DOM, 時間など)
その依存を「引数」や「オブジェクト」として外から渡せる形にできないか?
テストでは、どのレイヤーを本物としてテストし、どのレイヤーをモックにするか決めているか?
非同期関数は、副作用だけでなく「テストしやすい戻り値」を返すようにできないか?
モックをその場しのぎではなく、「再利用できる小さな実装」として設計できないか?
おすすめの練習は、
自分の非同期関数を一つ選んで、
まず「本物の依存」を全部列挙する
それを一つずつ「引数として渡す形」に書き換えてみる
簡単なモック実装を自分で書いて、テストから呼んでみる
という流れをやってみることです。
一度それができるようになると、
あなたの頭の中に「モック前提で設計するスイッチ」が入ります。
そのスイッチが入ったとき、
あなたはもう「非同期をなんとなくテストする人」ではなく、
「テストまで見据えて非同期を設計できるエンジニア」 にかなり近づいています。「モック化戦略」ってそもそも何の話?
非同期処理をテストしようとすると、必ずぶつかる壁があります。
それが「外部とのやり取り」です。
サーバーへのリクエスト
データベース
タイマー(setTimeout)
ブラウザの API(localStorage, IndexedDB など)
これらを本物のままテストで使うと、
テストが遅い
テストが不安定(ネットワーク次第)
再現性が低い(たまたま通る・たまたま落ちる)
という地獄になります。
そこで出てくるのが モック(mock) です。
「本物の代わりに、テスト用のニセモノを使う」戦略のことです。
ここでは、
非同期処理に特化して「どうモックを設計するか」を、
初心者向けにかみ砕いて話していきます。
まずは「何をモックするのか」をはっきりさせる
非同期でよくモック対象になるもの
非同期処理でモックしたくなるものは、だいたい決まっています。
ネットワーク(fetch, axios など)
時間(setTimeout, setInterval, Date.now)
ストレージ(localStorage, IndexedDB)
外部サービス SDK(Firebase, Stripe など)
ポイントは、
「テストを遅く・不安定にするもの」
「テスト環境に存在しないもの」
をモックの候補として見ることです。
そしてもう一つ大事なのが、
「モックしやすいようにコードを設計する」
という発想です。
モックは「テストのための裏技」ではなく、
設計の一部として最初から考えた方が、結果的に楽になります。
戦略1:依存を「引数として渡す」モック化
fetch をモックしやすい形にする
まずは一番シンプルで強力な戦略から。
「外部依存を引数として受け取る」スタイルです。
悪い例から見てみます。
async function fetchCurrentUser() {
const res = await fetch("/api/user");
if (!res.ok) {
throw new Error("ユーザー取得に失敗しました");
}
return res.json();
}
JavaScriptこの関数をテストしようとすると、
グローバルの fetch を上書きしたり、テストフレームワークの機能に頼ったりする必要が出てきます。
これを「モックしやすい形」に変えると、こうなります。
async function fetchCurrentUser(fetchImpl) {
const res = await fetchImpl("/api/user");
if (!res.ok) {
throw new Error("ユーザー取得に失敗しました");
}
return res.json();
}
JavaScript本番コードではこう呼びます。
const user = await fetchCurrentUser(fetch);
JavaScriptテストでは、fetchImpl にモックを渡せます。
async function fakeFetch() {
return {
ok: true,
json: async () => ({ id: 1, name: "テスト太郎" }),
};
}
const user = await fetchCurrentUser(fakeFetch);
// user.id === 1, user.name === "テスト太郎" をテストできる
JavaScriptここでの重要ポイントは、
「モックしやすさは、テストコード側ではなく“プロダクションコードの引数設計”で決まる」
ということです。
「あとからモックしたい」ではなく、
「最初からモックできる形にしておく」が正解に近いです。
戦略2:「インターフェース」を決めてモックを差し替える
fetch 以外にも応用できる形にする
さっきの fetchImpl は、実は「インターフェース」を決めているとも言えます。
「URL を受け取って、{ ok, json } を返す関数」
という約束を作っているわけです。
この「インターフェース」を意識すると、
他の非同期依存にも同じパターンを使えます。
例えば、ユーザーリポジトリを考えます。
async function getUserById(userRepository, id) {
return userRepository.findById(id);
}
JavaScript本番では、こういう実装を渡します。
const userRepository = {
async findById(id) {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error("ユーザー取得に失敗しました");
return res.json();
},
};
const user = await getUserById(userRepository, 1);
JavaScriptテストでは、こういうモックを渡します。
const fakeUserRepository = {
async findById(id) {
return { id, name: "モックユーザー" };
},
};
const user = await getUserById(fakeUserRepository, 1);
// user.name が "モックユーザー" であることをテスト
JavaScriptここでの戦略は、
「非同期依存を“オブジェクトのメソッド”としてまとめ、そのインターフェースをモックで再現する」
というものです。
関数一個ではなく、「役割ごとのオブジェクト」を渡すと、
テスト時に差し替えやすくなります。
戦略3:時間をモックする(タイマー・遅延のテスト)
setTimeout を直接使わない形にする
時間に依存する非同期処理も、モック化戦略がないとテストがつらくなります。
例えば、こういうコード。
function showMessageWithDelay(message) {
setTimeout(() => {
console.log(message);
}, 1000);
}
JavaScriptこれをテストしようとすると、
「1秒待つ」か、「テストフレームワークのタイマー機能に依存する」必要が出てきます。
ここでも同じ発想で、
「時間の仕組み」を注入できるようにします。
function showMessageWithDelay(message, { setTimeoutImpl }) {
setTimeoutImpl(() => {
console.log(message);
}, 1000);
}
JavaScript本番ではこう呼びます。
showMessageWithDelay("hello", { setTimeoutImpl: setTimeout });
JavaScriptテストでは、こう書けます。
const calls = [];
function fakeSetTimeout(fn, ms) {
calls.push({ fn, ms });
}
showMessageWithDelay("hello", { setTimeoutImpl: fakeSetTimeout });
// calls[0].ms が 1000 かどうかをテスト
// calls[0].fn() を自分で呼んで、console.log の結果を確認する
JavaScriptここでの重要ポイントは、
「時間に依存する非同期処理も、“時間 API” をモック可能な形にしておく」
ということです。
setTimeout を直接呼ぶのではなく、
「setTimeout 的なもの」を引数で受け取る設計にしておくと、
テストが一気に楽になります。
戦略4:戻り値を「テストしやすい形」にする
副作用だけの非同期関数はモック地獄になりやすい
例えば、こういう関数。
async function saveSettings(settings) {
const res = await fetch("/api/settings", {
method: "POST",
body: JSON.stringify(settings),
});
if (!res.ok) {
alert("保存に失敗しました");
return;
}
alert("保存しました");
}
JavaScriptこれをテストしようとすると、
fetch をモックする
alert をモックする
など、モックだらけになります。
ここでの戦略は、
「非同期関数に“意味のある戻り値”を持たせる」
ことです。
例えば、こう変えます。
async function postSettings(fetchImpl, settings) {
const res = await fetchImpl("/api/settings", {
method: "POST",
body: JSON.stringify(settings),
});
if (!res.ok) {
return { ok: false, error: new Error("保存に失敗しました") };
}
return { ok: true };
}
JavaScriptテストでは、こう書けます。
async function fakeFetchOk() {
return { ok: true };
}
async function fakeFetchFail() {
return { ok: false };
}
const successResult = await postSettings(fakeFetchOk, { theme: "dark" });
// successResult.ok === true
const failResult = await postSettings(fakeFetchFail, { theme: "dark" });
// failResult.ok === false, failResult.error.message をテスト
JavaScriptUI 側では、こう使います。
async function saveSettingsWithUI(settings) {
const result = await postSettings(fetch, settings);
if (!result.ok) {
alert(result.error.message);
} else {
alert("保存しました");
}
}
JavaScriptここでのポイントは、
「モックする対象を減らすために、“副作用を外に追い出し、非同期関数は結果だけ返すようにする”」
という発想です。
戦略5:モックの「粒度」を意識する
どこまでモックするかを決める
モック化戦略でよくハマるのが、
「モックしすぎて何をテストしているのか分からなくなる」状態です。
例えば、こんな構成を考えます。
fetch をモック
その上のリポジトリをモック
さらにその上のサービスをモック
こうなると、
「結局、本物はどこにもいない」状態になり、
テストの意味が薄くなります。
ここで大事なのは、
「どのレイヤーを“本物”としてテストし、どのレイヤーをモックにするか」
を意識して決めることです。
例えば、
リポジトリのテスト → fetch をモックして、リポジトリのロジックを本物でテスト
サービスのテスト → リポジトリをモックして、サービスのロジックを本物でテスト
UI のテスト → サービスをモックして、UI の振る舞いをテスト
というように、
「一段下のレイヤーだけをモックする」
というルールを決めると、テストの意味がはっきりします。
戦略6:モックを「使い捨て」ではなく「設計の一部」として考える
モックも「API の一種」として設計する
雑にモックを書くと、こうなりがちです。
const fakeFetch = jest.fn().mockResolvedValue({
ok: true,
json: async () => ({ id: 1 }),
});
JavaScriptこれでも動きますが、
テストファイルごとにバラバラなモックが増えていきます。
もう一歩進めて、
「モックも一つの実装」として設計してしまうのもアリです。
function createFakeFetchUser({ shouldFail = false } = {}) {
return async function fakeFetch(url) {
if (!url.startsWith("/api/user")) {
throw new Error("想定外の URL");
}
if (shouldFail) {
return { ok: false };
}
return {
ok: true,
json: async () => ({ id: 1, name: "モックユーザー" }),
};
};
}
JavaScriptテストでは、こう使えます。
const fetchOk = createFakeFetchUser();
const fetchFail = createFakeFetchUser({ shouldFail: true });
JavaScriptここでのポイントは、
「モックも“ちゃんとしたコード”として設計しておくと、再利用できるし、テストの意図も読みやすくなる」
ということです。
モックを「その場しのぎのニセモノ」ではなく、
「テスト用のもう一つの実装」として扱うイメージです。
初心者として「モック化戦略」で本当に意識してほしいこと
最後に、あなたの頭の中に置いておいてほしい問いをまとめます。
この非同期関数は、何と何に依存しているか?(fetch, DOM, 時間など)
その依存を「引数」や「オブジェクト」として外から渡せる形にできないか?
テストでは、どのレイヤーを本物としてテストし、どのレイヤーをモックにするか決めているか?
非同期関数は、副作用だけでなく「テストしやすい戻り値」を返すようにできないか?
モックをその場しのぎではなく、「再利用できる小さな実装」として設計できないか?
おすすめの練習は、
自分の非同期関数を一つ選んで、
まず「本物の依存」を全部列挙する
それを一つずつ「引数として渡す形」に書き換えてみる
簡単なモック実装を自分で書いて、テストから呼んでみる
という流れをやってみることです。
一度それができるようになると、
あなたの頭の中に「モック前提で設計するスイッチ」が入ります。
そのスイッチが入ったとき、
あなたはもう「非同期をなんとなくテストする人」ではなく、
「テストまで見据えて非同期を設計できるエンジニア」 にかなり近づいています。
