依存関係のモック化とは?テストしやすい設計と検証の進め方を解説
ソフトウェアのテストを進めていると、確認したいのは一つの小さなロジックなのに、その周辺にあるデータベース、外部API、ファイル操作、通知処理、認証処理などが一緒に絡んでしまい、思ったよりも重く、不安定で、原因の分かりにくいテストになってしまうことがあります。本来は「この条件ならこの分岐に入るか」「この値ならこの結果になるか」を見たいだけなのに、外部環境の状態や通信成否にまで引きずられると、単体テストとしての意味が薄くなりやすくなります。しかも、こうした外部要因はロジックそのものとは別の理由で失敗を生むため、テストが落ちたときに本当に直すべき箇所が見えにくくなることも少なくありません。こうした場面で重要になるのが、依存関係をそのまま使うのではなく、置き換えて検証するという考え方です。
依存関係のモック化は、単なる便利なテクニックではありません。テスト対象そのものへ集中しやすくし、実行速度を安定させ、結果の再現性を高め、失敗したときの切り分けを容易にするための設計上の工夫でもあります。その一方で、使い方を誤ると、実装詳細に寄りすぎた壊れやすいテストになることもあります。つまり、モック化は「使えばよい技法」ではなく、「何を守りたいテストなのか」を踏まえて使い分けるべき手段だと考える必要があります。この記事では、依存関係のモック化とは何か、どんな依存が対象になりやすいのか、なぜ必要になるのか、どうすれば差し替えやすい設計へ寄せられるのか、さらに使いすぎを避けるためには何を意識すべきかまで、実務の流れに沿って整理していきます。
1. 依存関係のモック化とは
依存関係のモック化とは、テスト対象が利用している外部の要素を、そのまま本物で使うのではなく、テスト用に置き換えて確認する考え方です。ここでいう依存関係には、データベース接続や外部API呼び出しだけでなく、時刻取得、乱数生成、ファイル操作、認証処理、通知送信など、テスト対象の外側にあり、結果や速度や安定性へ影響するさまざまな要素が含まれます。つまり、モック化とは「本物を捨てること」ではなく、「今確認したい対象以外の変動要因をいったん制御可能な形に置き換えること」だと言えます。外部依存を減らすこと自体が目的ではなく、テストの焦点をぶらさないことが本質です。
この考え方が重要なのは、テストの目的をぶらさないためです。単体テストで確認したいのは、多くの場合、業務ロジックや条件分岐、値の変換、例外時の振る舞いといった比較的小さな責務です。そこへ本物の外部依存が多く入り込むと、テストが遅くなり、不安定になり、失敗理由が外部要因なのかロジックなのか分かりにくくなります。しかも、テスト対象の周辺にある依存まで一緒に確認し始めると、単体テストのつもりでも実質的には結合テストのような重さを持つようになります。だからこそ、依存関係のモック化は、テストしやすさを高めるための中心的な手段の一つとして扱われます。
1.1 依存先をそのまま使わずに検証する考え方
依存先をそのまま使わずに検証するというのは、単に外部処理を省略するという意味ではありません。大切なのは、今そのテストで確認したいことに対して、外部依存が必要以上に影響しないようにすることです。たとえば、ユーザー情報を受け取って条件分岐する処理を確認したいなら、実際にDBへ接続してレコードを取りにいく必要はないかもしれません。必要なのは「この条件のユーザー情報が返ってくる」という前提であり、その前提を安定して作れるなら、本物のDBより置き換えた依存のほうが単体テストとしては適しています。つまり、依存先の存在を消すのではなく、確認対象にとって必要な形だけを残す発想が重要です。
また、この考え方は、テストの責務を明確にすることにもつながります。本物の依存を使ってしまうと、単体テストのつもりでも、実際には結合的な確認が混ざりやすくなります。そうなると、ロジックの問題を見たいのか、接続や連携の問題を見たいのかが曖昧になります。依存をそのまま使うことで確認範囲が広がるのは一見よさそうに見えますが、失敗したときにはむしろ原因が見えにくくなります。つまり、依存先を置き換えるという発想は、テスト対象の責務境界をはっきりさせるための考え方でもあります。
1.2 単体テストで必要になりやすい理由
依存関係のモック化が単体テストで特に重要になるのは、単体テストが本来、小さな責務を速く、安定して、原因を絞り込みやすい形で確認するためのものだからです。もし毎回本物のDBや外部APIや通知基盤まで含めてしまうと、テスト実行は重くなり、ネットワーク状態や環境差分にも左右されやすくなります。その状態では、単体テストの持つ即時フィードバックという価値がかなり薄れてしまいます。少しコードを直してすぐ確かめる、というリズムが崩れると、単体テストは存在していても日常的に使われにくくなります。
さらに、単体テストは実装中や改修中に何度も繰り返し実行することが多いため、速さと再現性がとても重要です。少し直してすぐ確かめたいのに、毎回外部依存が絡んで準備や待ち時間が必要になると、開発テンポが落ちやすくなります。また、失敗したときも、外部依存が多いほど切り分けに時間がかかります。だからこそ、依存関係のモック化は、単体テストを単体テストらしく保つために必要になりやすいのです。単体テストが軽く保たれているかどうかは、日々の開発効率にも直結します。
実依存を使う場合とモック化する場合の違い
| 観点 | 実依存を使う場合 | モック化する場合 |
|---|---|---|
| 実行速度 | 遅くなりやすい | 比較的速い |
| 再現性 | 環境に左右されやすい | 安定しやすい |
| 切り分けやすさ | 外部要因が混ざりやすい | テスト対象へ絞りやすい |
| 単体テストとの相性 | 低くなりやすい | 高い |
| 実連携の確認 | できる | できない |
1.3 どのような場面で使われるか
依存関係のモック化は、DBアクセスやAPI呼び出しがある場面だけで使われるわけではありません。たとえば、時刻によって有効期限判定が変わる処理、乱数によって結果が変動する処理、メール送信や通知送信のように副作用を持つ処理、環境変数や設定ファイルによって分岐する処理などでも広く使われます。つまり、モック化は「大きな外部連携」のためだけのものではなく、「テストを不安定にしやすい依存」全般に対して考えるべきものです。外から見て小さく見える依存でも、結果の再現性を崩すなら十分にモック化の候補になります。
実務では、とくに外部環境が重い、遅い、失敗しやすい、あるいは操作すると実際の副作用が出てしまう依存でモック化の価値が高まります。メール送信を本当に飛ばしたくない、通知を本番の送信基盤へ流したくない、現在時刻が毎回変わるせいでテストがぶれる、といった状況では、依存先を置き換えることで確認したいロジックへ集中しやすくなります。また、異常系や境界値を意図的に作りたい場合にも、本物よりモックのほうが扱いやすいことが多いです。
1.4 単なる代用品では終わらない意義
モック化は、一見すると本物の代用品を置いているだけのように見えるかもしれません。しかし実際には、それ以上の意義があります。依存先をどう置き換えるかを考える過程で、どこが業務ロジックで、どこが入出力で、どこが外部境界なのかが見えやすくなります。つまり、モック化はテスト技法であると同時に、設計の境界を整理するためのレンズにもなります。差し替えにくい依存があるなら、その場所は責務が混ざっている可能性があります。
また、モック化をしやすいかどうかは、そのままコードの差し替えやすさ、責務分離のしやすさ、テスト容易性の高さを映していることがあります。依存を切り出しにくいコードは、しばしば責務が混ざっていたり、具体実装へ強く結びついていたりします。その意味で、モック化は単なる代用品の話ではなく、設計の状態を見直すきっかけにもなります。テストのためにモックを考えることが、結果として設計改善へつながることは少なくありません。
1.5 テスト対象そのものに集中するための手段として見る
依存関係のモック化を理解するときに大切なのは、「外部依存を消すこと」が目的ではなく、「テスト対象そのものへ集中できる状態を作ること」が目的だと考えることです。確認したいのが条件分岐なら、その分岐に必要な前提だけを安定して作れればよく、実際の通信や接続は今その場では不要かもしれません。つまり、モック化は確認対象以外を見ないようにするための遮蔽手段として捉えると分かりやすくなります。余計な変動要因をいったん脇へ寄せることで、見たい振る舞いが見えやすくなります。
この視点があると、どこまでモック化すべきかも判断しやすくなります。目的がロジック確認なら外部依存はできるだけ置き換え、目的が実際の接続確認なら結合テストへ回す、という整理がしやすくなるからです。依存関係のモック化は、テストを軽くするためだけではなく、テストの目的を守るための手段として見るべきです。何を守りたいテストなのかがはっきりしていれば、モック化の使い方も自然と整理しやすくなります。
2. モック化の対象になりやすい依存関係
モック化の対象になりやすい依存関係にはいくつか典型があります。共通しているのは、テスト対象の外側にあり、結果の再現性や実行速度、失敗時の切り分けやすさに影響しやすいことです。たとえば、DBアクセス、外部API呼び出し、通知送信、時刻取得、環境変数読み取りなどは、本物を使うと単体テストの目的から外れやすくなります。つまり、モック化の対象は「外部っぽいもの全部」ではなく、「今のテストにとって変動要因になりやすいもの」と考えると整理しやすくなります。ここを広く捉えすぎると何でもモックにしたくなり、逆に狭く捉えすぎると不安定要因が残りやすくなります。
また、どの依存をモック化するかは、単に技術分類だけで決めるべきではありません。同じDBアクセスでも、単体テストでは置き換えたいが、結合テストでは本物をつなぎたいということがあります。つまり、対象になりやすい依存を知ることは大切ですが、それ以上に「このテストでは何を確認したいか」と合わせて考える必要があります。依存の種類だけでなく、テストの役割との組み合わせで判断することが重要です。
2.1 データベースアクセス
データベースアクセスは、モック化の代表例の一つです。理由は分かりやすく、接続準備やデータ投入が必要になりやすく、テスト速度が落ちやすいうえに、データ状態によって結果がぶれやすいからです。単体テストで確認したいのが、取得したデータに応じた業務分岐や値変換であるなら、実際にDBへ問い合わせる必要はないことが多いです。必要なのは「こういうレコードが返ってきた」という条件であり、その条件を安定して再現できるなら、モック化したほうが単体テストとしては適しています。
さらに、DBアクセスをモック化することで、該当レコードなし、重複状態、保存失敗、例外発生といった条件も意図的に作りやすくなります。実DBでこうした条件を毎回整えるのは面倒ですが、置き換えた依存なら短く明示的に表現しやすくなります。また、保存処理を本当に実行しなくても、どの値で保存しようとしたかを確認できることもあります。そのため、DBアクセスは実務でもモック化の候補になりやすい依存です。
2.2 外部API呼び出し
外部API呼び出しも、モック化がよく使われる対象です。通信待ちや外部サービス側の状態によって結果が揺れやすく、ネットワーク障害やレート制限の影響も受けやすいため、単体テストの中へそのまま持ち込むと不安定になりやすいからです。とくに、成功レスポンスだけでなく、タイムアウト、認証失敗、部分欠損レスポンスなどを確認したい場合は、モック化によって条件をかなり作りやすくなります。本物では再現が難しい障害パターンも、置き換えた依存なら比較的簡単に表現できます。
また、外部APIの本物を呼ぶと、費用、実行回数制限、検証環境制約など現実的な問題も起こりがちです。検証中に相手側の仕様変更やメンテナンスに引っ張られることもあります。そのため、API呼び出しがあるコードでは、ロジック確認を単体テストで行い、実接続確認は別の結合テストや疎通確認へ分ける考え方がよく使われます。確認したいことを切り分けるうえでも、外部APIは典型的なモック化対象です。
代表的なモック化対象
- 時刻取得
- 乱数生成
- ファイル入出力
- 通知処理
- 認証処理
- 環境変数や設定値の取得
- 外部サービスクライアント
- キュー送信やジョブ投入
2.3 メール送信や通知処理
メール送信や通知処理は、副作用を持つ依存としてモック化の対象になりやすいです。テストのたびに本当にメールが飛んだり、通知基盤へイベントが流れたりすると、検証環境を汚したり、誤送信のような問題につながったりします。単体テストで見たいのは、通常「送信されたかどうか」や「どんな内容を送ろうとしたか」であって、実際に届くことではない場合が多いです。つまり、通知処理は呼び出し確認や引数確認の対象としてモック化しやすい依存です。
また、通知処理では「送るべきでないケースで送っていないか」も重要な確認ポイントになります。条件分岐やフラグの組み合わせに応じて通知有無が変わる場合、本物の通知よりモックのほうが挙動を追いやすくなります。通知そのものが業務的に重要でも、単体テスト段階では送信の事実より「送ろうとした判断」のほうを見たいことが多いです。そのため、副作用確認という観点でもモック化が有効です。
2.4 時刻や環境情報への依存
時刻取得や環境情報への依存は見落とされやすいですが、テストを不安定にしやすい典型例です。現在時刻によって有効期限判定や公開状態が変わる処理、環境変数によって分岐する処理は、本物の値をそのまま読むとテスト結果が時刻や環境に左右されやすくなります。こうした依存は外部APIほど大きく見えないため、ついそのまま使われがちですが、再現性への影響は決して小さくありません。つまり、こうした依存は小さく見えても、再現性を崩しやすい重要な対象です。
時刻を固定したり、環境設定を差し替えたりできるようにしておくと、境界条件や特定状態を意図的に作りやすくなります。たとえば「有効期限当日のみ通る」や「本番フラグがONのときだけ通る」といった条件も、外部依存をそのまま読むより安定して表現できます。そのため、時刻や設定値のような一見軽い依存も、モック化対象として意識しておく価値があります。小さな依存ほど見落としやすいため、早めに整理しておくと効果的です。
2.5 外部サービス連携を含む処理
決済、認証基盤、ストレージ、チャット通知、検索エンジンなど、外部サービスとつながる処理は、単体テストでは特にモック化の恩恵が大きいです。これらは成功時の応答だけでなく、失敗種類も多く、単体テストの段階で本物を毎回つなぐのは現実的でないことが多いからです。外部連携を含む処理では、戻り値やエラー種別を意図的に作ることで、テスト対象がどう振る舞うかを比較的短いコードで確認しやすくなります。また、外部サービス自体が重く、環境制約や利用制限を持つことも多いため、単体テストへそのまま持ち込むコストが高くなりがちです。
一方で、外部サービスとの本当の契約整合性まではモックでは確認できません。たとえば、レスポンスの実際の構造変更や、認証設定のずれ、送信先の仕様差分などは、本物をつないで初めて見える問題です。だからこそ、単体テストではモック化でロジックを見て、結合テストや疎通確認で実連携を見るという役割分担が重要になります。モック化は実連携確認の代わりではなく、役割を切り分けるための手段として考えるべきです。
3. なぜ依存関係をモック化するのか
依存関係をモック化する理由は一つではありません。実行速度を速く保つこと、テスト結果の再現性を高めること、外部環境の影響を減らすこと、失敗原因を絞り込みやすくすること、そして単体テストとしての役割を守ることなど、複数の目的があります。つまり、モック化は単なるテクニックというより、単体テストが本来持つべき性質を支えるための方法だと整理したほうが分かりやすいです。モック化の価値は、便利さそのものより、テストの性質を壊さずに保てることにあります。
また、これらの理由は独立しているようでいて、実際には互いに関係しています。たとえば、再現性が高いと失敗原因を絞りやすくなり、外部環境の影響が減ると速度も安定しやすくなります。速度が安定すれば日常的に回しやすくなり、結果として単体テストが開発フローに組み込みやすくなります。そのため、モック化の価値は個別の利点よりも、「テスト全体を扱いやすくする効果」として見るのが自然です。何か一つのメリットだけで判断するより、全体の扱いやすさが上がるかどうかで考えると整理しやすくなります。
3.1 実行速度を安定させる
依存関係をモック化する大きな理由の一つは、実行速度を安定させやすいことです。DB接続、外部API通信、ファイル操作、メール送信などを本物で行うと、どうしても待ち時間や準備コストが増えます。しかも、環境やタイミングによってばらつきも出やすくなります。単体テストは、実装中や改修中に何度も素早く回せることに価値があるため、この重さは相性がよくありません。つまり、モック化はテストを速くするだけでなく、速さを安定させるためにも重要です。安定した速度は、テストを気軽に回せる状態を支えます。
実行速度が安定すると、開発者が気軽に回しやすくなり、テストが日常的なフィードバックとして機能しやすくなります。逆に、遅くて重いテストは後回しにされやすく、結果としてテストがあるのに回されない状態にもつながります。また、速度のばらつきが少ないと、CIでの運用もしやすくなります。その意味でも、速度の安定化は単なる快適さではなく、テスト運用の質に直結します。モック化はその土台を作る手段の一つです。
3.2 テスト結果の再現性を高める
依存関係をモック化すると、テスト結果の再現性を高めやすくなります。本物の外部依存を使う場合、ネットワーク状態、DB内容、システム時刻、環境設定など多くの要因が結果へ影響します。こうした変数が多いと、昨日は通ったのに今日は落ちる、手元では通るのにCIでは落ちる、といった不安定さが起きやすくなります。しかも、それがコード起因なのか、単なる環境差なのかが分かりにくくなります。つまり、モック化はテストを安定して繰り返すための条件づくりでもあります。
再現性が高いと、失敗時の調査もやりやすくなります。同じ条件を何度でも再実行できれば、修正前後の比較もしやすくなり、原因切り分けも早くなります。また、チーム内で「このケースではこう落ちる」と共有しやすくなる点でも効果があります。だからこそ、単体テストでは変動要因をできるだけ減らすことが重視され、その手段としてモック化が使われます。再現性の高さは、テストそのものの信頼性を支える重要な要素です。
速度、再現性、切り分けやすさ、失敗要因の少なさの整理
| 観点 | モック化しない場合 | モック化する場合 |
|---|---|---|
| 速度 | 外部依存の重さを受けやすい | 比較的軽く安定しやすい |
| 再現性 | 環境や時刻に左右されやすい | 条件を固定しやすい |
| 切り分け | 外部要因が混ざりやすい | 対象ロジックへ寄せやすい |
| 失敗要因 | 多くなりやすい | 減らしやすい |
3.3 外部環境の影響を減らす
モック化の大きな役割は、外部環境の影響を減らすことです。テスト対象が本来正しくても、外部APIが一時的に落ちている、検証DBの状態が想定と違う、メール基盤が遅延しているといった要因で失敗してしまうと、単体テストとしての意味が薄れます。単体テストは環境疎通の確認ではなく、小さな責務の確認であるべきなので、外部環境へ引きずられすぎない構造が望ましいです。確認したいロジックが正しいのに環境のせいで落ちる状態は、単体テストとしてはかなり扱いにくいです。
また、環境依存が少ないと、開発者の手元でもCIでも同じように回しやすくなります。環境差分が減ることで、「手元では再現しない」という状況も減らしやすくなります。こうした一貫性は、チーム全体でテストを共有しやすくするうえでも重要です。そのため、モック化は個人の利便性だけでなく、チーム運用上の安定性にも寄与します。外部環境の影響を減らすことは、開発体験の改善だけでなく、運用品質の改善にもつながります。
3.4 失敗原因を絞り込みやすくする
テストが失敗したとき、原因がロジックなのか、DB状態なのか、通信なのか、設定なのかが分かりにくいと、修正までに時間がかかります。依存関係をモック化しておくと、少なくともそのテストでは外部要因をかなり減らせるため、失敗原因を対象ロジックへ寄せやすくなります。単体テストに求められるのは、速く落ちることだけでなく、「なぜ落ちたか」が見やすいことでもあります。つまり、モック化はテストの読みやすさだけでなく、障害分析のしやすさにもつながります。
とくに、異常系やエラーハンドリングのテストでは、狙った失敗条件を意図的に作れるかが大切です。本物の外部依存では再現しづらい失敗も、モック化しておけば比較的短い準備で表現しやすくなります。また、例外の種類や戻り値のパターンを明示的に作れるため、「この場合にどう振る舞うべきか」をかなり絞って確認できます。これも、切り分けやすさを高める一因です。
3.5 単体テストとしての役割を保ちやすくする
依存関係をモック化する最大の理由の一つは、単体テストとしての役割を保ちやすくすることです。単体テストは、本来ひとつの責務や小さな振る舞いを対象にし、速く、安定して、明確な失敗を返すことに価値があります。そこへ本物の依存を多く持ち込むと、確認対象が広がりすぎて、結局何を見ているのか曖昧になります。単体テストの中へ実連携まで持ち込むと、軽い確認と重い確認の境界が崩れやすくなります。つまり、モック化は単体テストを単体テストのままに保つための仕組みでもあります。
もちろん、実際の連携まで含めた確認が不要という意味ではありません。それは結合テストやE2Eで別に見るべきものです。単体テストでは、テスト対象そのものの責務へ集中する。そのための整理手段として、依存関係のモック化が使われるのです。この役割分担が見えていると、単体テストと結合テストの両方を無理なく設計しやすくなります。
4. モック化しやすい設計へどう寄せるか
依存関係をモック化したくても、コード構造がそれを許さないことがあります。テスト対象の中で直接DBクライアントをnewしていたり、グローバルな設定や現在時刻をその場で読んでいたりすると、差し替えが難しくなります。こうした構造では、モックを使いたくてもテスト対象の内部へ強く入り込まなければならず、結果としてテストが書きにくくなります。つまり、モック化はテスト時の小技ではなく、差し替えやすい設計とセットで考えるべきものです。
この点が重要なのは、モック化のしやすさがそのままテスト容易性や責務分離の状態を映しやすいからです。依存を外から受け取れる設計は、変更にも強く、実装差し替えもしやすくなることが多いです。また、境界が明確になることで、どこが業務ロジックでどこが入出力なのかも見えやすくなります。そのため、モック化を考えることは、設計改善を考えることにもつながります。テストのために設計を見ることが、結果としてコード全体の見通しを良くすることがあります。
4.1 依存関係を外から受け取る
モック化しやすい設計の基本は、依存関係を外から受け取れるようにすることです。たとえば、コンストラクタや引数でリポジトリ、APIクライアント、時刻取得オブジェクトなどを渡せるようにしておけば、テスト時には本物ではなく置き換えた依存を渡しやすくなります。内部で勝手に依存を生成する構造より、外から注入できる構造のほうが差し替え点を明確にしやすくなります。つまり、依存を内部で固定的に作るのではなく、注入できる形にすることが差し替えやすさの出発点です。
この形にしておくと、テストのためだけでなく、実装の切り替えや将来的な構成変更にも対応しやすくなります。たとえば、APIクライアントの実装を差し替える、キャッシュ付きリポジトリへ切り替える、といった変更もやりやすくなります。依存注入は大げさなフレームワークの話ではなく、「どこで依存を決めるか」をずらす設計上の工夫だと考えると理解しやすいです。モック化しやすい設計は、そのまま柔軟な実装にもつながります。
4.2 具体実装へ直接つながない
テスト対象が具体クラスへ直接依存していると、差し替えの自由度が下がりやすくなります。たとえば、特定のDBライブラリクラスや特定の外部クライアントをそのまま握っていると、テスト時に別実装へ切り替えにくくなります。また、具体実装に引っ張られるほど、テストがライブラリ都合や外部仕様へ寄りやすくなります。つまり、具体実装へ直接つながないように境界を設けることが、モック化しやすさを高めます。
このとき大切なのは、抽象化そのものが目的ではないということです。何でもインターフェース化すればよいのではなく、外部依存との境界で差し替えたい責務があるなら、その点を明確にするために抽象を置く、という考え方が自然です。抽象を増やすことではなく、境界を見やすくすることが重要です。必要な場所だけを小さく抽象化するほうが、実務では扱いやすくなります。
差し替えやすくするための代表的な考え方
- 引数やコンストラクタで依存を渡す
- 生成処理を別の場所へ分ける
- 外部境界を小さなインターフェースで包む
- 入出力処理と業務ロジックを分ける
- 差し替え点をコード上で見える形にする
4.3 入出力と業務ロジックを分ける
モック化しやすい設計に寄せるには、入出力と業務ロジックを分けることが重要です。データ取得、API呼び出し、ファイル保存のような外部接点と、値判定や業務ルールのような中心ロジックが一つの関数へ混ざっていると、外部依存を切り離しにくくなります。こうした構造では、少しの確認にも本物の依存がついて回りやすくなります。つまり、モック化しやすいかどうかは、責務分離ができているかどうかとかなり深く関係しています。
入出力を外側へ寄せ、ロジックを内側へ寄せると、ロジック部分だけを安定して確認しやすくなります。また、外部依存の種類が変わっても中心ロジックを保ちやすくなるため、設計全体の見通しもよくなります。単体テストで確認したいのは多くの場合この内側のロジックなので、そこを外部依存から切り離せるかどうかがテストしやすさに直結します。モック化しやすい設計は、ロジックの中心が見えやすい設計でもあります。
4.4 差し替え点を見える形にする
依存を差し替えられるようにしていても、その点が見えにくいと運用しづらくなります。どこで依存を受け取り、どこを本物にし、どこをテスト用に置き換えるのかが読み取れないと、テストコードも複雑になりやすいです。差し替え可能であることと、差し替えやすいことは少し違います。つまり、モック化しやすい設計では、差し替え点が「ある」だけでなく、「見える」ことも大切です。
コードを読んだときに、「ここが外部依存との境界で、ここなら置き換えられる」と分かる状態なら、テストを書く人も設計意図を追いやすくなります。また、新しく入ったメンバーでもモック化の対象を見つけやすくなります。そのため、差し替え点の明示性は、テスト容易性と可読性の両方に効いてきます。見える構造は、長く保守するときに特に強みになります。
4.5 テスト時だけ特別扱いしない構造にする
モック化しやすい設計を考えるとき、注意したいのは「テストのためだけの特別な分岐」を埋め込まないことです。たとえば、if test mode のような条件で本物とテスト用を切り替える構造は、一見便利ですが、実装側へテスト都合の複雑さを持ち込みやすくなります。その結果、本番コードがテスト事情に引きずられ、責務が曖昧になることもあります。つまり、望ましいのはテスト時だけ特別扱いするのではなく、通常構造の中で自然に差し替えられる設計です。
この状態にできると、本番コードとテストコードの責務分担も明確になり、テストのために本番ロジックが歪むのを防ぎやすくなります。テスト時だけの裏口を作るのではなく、最初から差し替え可能な構造として設計しておくことが理想です。そのほうが、本番・テスト双方で構造の一貫性も保ちやすくなります。モック化しやすさは、特別扱いの多さではなく、通常構造の素直さで決まることが多いです。
5. モック化したテストで何を確認するか
依存関係をモック化したときに何を確認すべきかは、そのテストの目的によって変わります。戻り値に応じた分岐を見たいのか、依存先が呼ばれたかを見たいのか、何回呼んだかを見たいのか、どの値を渡したかを見たいのか、失敗時にどう振る舞うかを見たいのかによって、確認すべきポイントは異なります。つまり、モック化しただけでよいのではなく、「何を観測したいテストなのか」を明確にしておくことが重要です。観測できることが増えるからこそ、何を見るかを絞る必要があります。
また、確認項目を増やしすぎると、実装詳細へ寄りすぎた壊れやすいテストになりやすくなります。そのため、モック化したテストでは、観測できることを何でも確認するのではなく、目的に直結する項目だけを選ぶ意識が必要です。一つのテストで戻り値も呼び出し回数も引数も順序も全部固定すると、可読性も保守性も下がりやすくなります。重要なのは、見たい振る舞いを明確にすることです。
5.1 戻り値に応じた分岐
モック化したテストで最もよく確認されるのが、戻り値に応じた分岐です。依存先から成功結果が返ったとき、空結果が返ったとき、例外相当の結果が返ったときなどに、テスト対象がどう振る舞うかを見るのは典型的な使い方です。たとえば、認証成功なら処理継続、認証失敗なら例外、データ未存在なら別の分岐、といった確認は、依存先の返り方を固定しやすいモックと相性が良いです。戻り値を変えることで、対象ロジックの分岐をかなり明確に確認できます。
このようなテストでは、依存先そのものの挙動を見たいのではなく、「その結果を受けたテスト対象の判断」を見たいのが本質です。つまり、戻り値確認中心のモック化では、依存先を入力装置のように使い、対象ロジックの分岐を確かめることになります。本物の依存を通す必要がないからこそ、分岐条件へ集中しやすくなります。モックの戻り値は、対象ロジックの条件を切り替えるための手段として考えると分かりやすいです。
5.2 依存先の呼び出し有無
依存先の呼び出し有無を確認するのも、モック化したテストの代表的な観点です。ある条件では通知が飛ぶべきで、別の条件では飛ばないべき、といったケースでは、実際に通知内容を送る必要はなく、「呼ばれたかどうか」を見れば十分なことがあります。副作用を持つ処理では、結果そのものより「副作用が起きるべきかどうか」が確認対象になることも多いです。つまり、呼び出し有無の確認は、副作用を持つ依存の扱いと特に相性が良いです。
ただし、この種の確認はやりすぎると実装詳細へ寄りやすくなります。呼び出されたかどうかが本当に振る舞いとして重要なのか、それとも別の観点で結果を見たほうがよいのかを判断する必要があります。たとえば、画面へ表示される結果が重要なら、内部の通知呼び出しを細かく固定しないほうがよいこともあります。呼び出し有無は便利ですが、常に最優先の確認とは限りません。何を確認したいテストなのかを先に決めることが大切です。
戻り値確認、呼び出し確認、回数確認、引数確認の違い
| 確認観点 | 何を見るか | 向いている場面 |
|---|---|---|
| 戻り値確認 | 依存先の返却結果による分岐 | 条件分岐、例外処理 |
| 呼び出し確認 | 呼ばれたかどうか | 副作用の有無、通知処理 |
| 回数確認 | 何回呼ばれたか | 重複防止、ループ処理 |
| 引数確認 | 何を渡したか | パラメータ組み立て確認 |
5.3 呼び出し回数
呼び出し回数の確認は、重複実行を防ぎたい場面や、特定条件で一回だけ処理されるべきことを確認したい場面で使われます。たとえば、通知は一回だけ送る、リトライは最大二回まで、ループ中で条件一致時のみ一度保存する、といったケースです。こうした場面では、回数確認によって不要な再実行や過剰な副作用を防げているかを見やすくなります。回数がそのまま業務上の意味を持つ場合には、とても有効な観点になります。
一方で、回数確認はかなり実装詳細に近づきやすい観点でもあります。そのため、本当に回数が振る舞いとして重要なケースだけに絞ることが大切です。何となく確認できるから毎回入れる、という使い方は避けたほうがよいです。たとえば内部処理をまとめたり分割したりしただけでテストが壊れるなら、その回数確認は強すぎる可能性があります。回数確認は有効ですが、必要なところだけに限定して使うほうが壊れにくくなります。
5.4 渡した値や条件
依存先へどんな値を渡したかを確認することもあります。たとえば、APIへ組み立てたリクエスト内容、通知メッセージの件名、DB保存時のパラメータ、検索条件の引数などが正しいかを見るケースです。これは、テスト対象が外部依存との境界で値をどう変換したかを確認したいときに有効です。つまり、引数確認は、依存先そのものではなく、対象が境界で何を渡そうとしたかを見る確認です。出力の組み立てが重要な処理では特に意味があります。
ただし、引数確認も細かくやりすぎると、少しの実装変更で壊れやすくなります。重要な項目だけを見ているのか、それとも内部の組み立て詳細まで固定してしまっているのかは意識して見分ける必要があります。たとえば、全文字列一致まで固定するより、重要フィールドだけ確認したほうがよい場面もあります。境界で何を保証したいかを見極めたうえで確認項目を選ぶことが大切です。
5.5 失敗時の振る舞い
モック化したテストでは、失敗時の振る舞いもかなり確認しやすくなります。依存先が例外を投げたとき、空結果を返したとき、失敗レスポンスを返したときに、対象がどう扱うかを意図的に作れるからです。実際の外部依存では再現しにくい障害条件でも、モックなら比較的簡単に用意できます。たとえば、タイムアウトや一時的な外部障害のようなケースも、ロジック確認という観点ではかなり試しやすくなります。つまり、エラーパターンの確認において、モック化は非常に有効です。
このとき重要なのは、単に失敗したことではなく、その失敗をどう扱うべきかを見ることです。再試行するのか、例外に変換するのか、ログだけ残して続行するのか、ロールバックするのかなど、失敗後の振る舞いは多様です。つまり、異常系モックは失敗条件そのものより、その後の判断を確認するために使う意識が大切です。障害時の振る舞いを意図的に作れることは、モック化の大きな強みの一つです。
6. モック化を使いすぎると何が起こるか
依存関係のモック化は便利ですが、使いすぎると別の問題が出てきます。とくに、実装詳細へ寄りすぎたテストになりやすいこと、振る舞いより呼び出し確認へ偏りやすいこと、テストが壊れやすくなりやすいこと、実際の連携不具合を見逃しやすいことはよくある課題です。モック化は単体テストを強くしてくれますが、同時に「見えすぎる」ことで確認しすぎを招きやすい面もあります。つまり、モック化は強い道具ですが、広く使えばよいものではなく、役割を限定して使うほうが健全です。
また、モック化を増やしすぎると、準備コードが長くなり、読み手にとって何を確認したいテストなのか分かりにくくなることがあります。モックの設定、返却値、呼び出し回数、引数確認が増えるほど、肝心の期待結果が埋もれやすくなります。便利な観測点が多いからこそ、意図的に絞らないとテストがノイズだらけになりやすいのです。モック化は「できるからやる」ではなく、「必要だから使う」という姿勢が重要です。
6.1 実装詳細に寄りすぎたテストになる
モック化したテストで起こりやすい問題の一つは、振る舞いではなく実装手順そのものを固定してしまうことです。たとえば、「この順でこのメソッドを呼ぶ」「この内部関数を一回だけ呼ぶ」といった確認は、表面的には厳密に見えますが、実装の少しの組み替えでも壊れやすくなります。外から見た結果は同じでも、内部手順を変えただけでテストが落ちるようでは、保守性が低くなります。つまり、モック化を使うときは、どこまでが本当に守りたい振る舞いで、どこからがただの実装詳細かを見極める必要があります。
この問題が起きる背景には、モックを使うと内部の呼び出しが見えやすくなることがあります。見えるから確認したくなるのですが、それが常に価値のある確認とは限りません。内部詳細の固定ではなく、利用者や呼び出し元から見て重要な振る舞いを優先する姿勢が必要です。テストが壊れたときに「振る舞いが変わったから落ちた」のか「中身の順番が変わっただけで落ちた」のかを見分けることが重要になります。
6.2 振る舞いより呼び出し確認に偏る
モック化したテストでは、呼び出されたか、何回か、何を渡したかを簡単に見られるため、ついそこばかり確認したくなることがあります。しかし、呼び出し確認ばかりに偏ると、「この処理が何を保証しているのか」より「内部でどのメソッドをどう使ったか」ばかりが前面に出てしまいます。そうなると、テストは書けているのに、何を守っているのかが読みにくくなります。つまり、テストの重心が外から見た振る舞いではなく、内部の手続きに偏りやすくなります。
もちろん、通知送信のように呼び出しそのものが意味を持つ場面もあります。ただし、その場合でも本当に必要な呼び出し確認だけに絞るべきです。確認できる項目が多いからといって、全部を固定してしまうと読みづらく壊れやすいテストになります。重要なのは、「その呼び出しが業務上意味を持つかどうか」です。確認できることすべてをテストに入れる必要はありません。
モック化を使いすぎたときに起きやすい問題
- テストが壊れやすくなる
- 意図より準備コードが目立つ
- 呼び出し確認ばかりで読みにくくなる
- 設計をモック都合で硬くしやすい
- 実際の連携不具合を別で見ないままになる
6.3 テストが実装変更に弱くなる
モックを多用しすぎると、テストが実装変更にとても弱くなることがあります。たとえば、内部の責務分担を少し整理しただけ、メソッド呼び出し順を少し変えただけ、ループを別関数へ切り出しただけで大量に落ちるような状態です。こうした落ち方は、振る舞いが変わったからではなく、内部構造を固定しすぎたことに由来しています。結果として、設計を改善したいのにテストが邪魔をする状態にもなりやすいです。つまり、モック化を使うときは、将来の設計変更に耐えられるかも考える必要があります。
この問題を避けるには、テスト対象の外から見える意味を中心に確認し、内部の連携確認は本当に必要な最小限へ絞ることが大切です。変化してもよい部分をテストで固定しない、という意識が重要です。また、リファクタリングのたびに大量に落ちるテストは、保守コストだけでなく改善意欲も下げやすくなります。テストは改善を支えるものであって、改善を止めるものであってはいけません。
6.4 意図が読み取りにくくなる
モック設定が増えすぎると、テストコードの読み手は「このテストで何を確認したいのか」が分かりにくくなります。戻り値設定、呼び出し回数、引数一致、順序確認が一つのテストへ大量に詰め込まれると、肝心の期待結果が埋もれやすくなるからです。見た目には詳細に確認しているようでも、読む側からすると重要な観点が見つけにくくなります。つまり、モック化のしすぎはテストの可読性も下げやすいです。
実務では、テストは書くことだけでなく、後から読むことや直すことのほうが多くなります。そのため、テストの意図がすぐ伝わる構造を保つことはとても重要です。モックが多いほど、なおさら一つのテストで何を見ているのかを絞る必要があります。読みやすさを守るには、確認項目を減らす勇気も必要です。すべてを一つで確認するより、目的ごとに分けたほうが伝わりやすくなります。
6.5 実際の連携不具合を見逃しやすくなる
モック化したテストでは、依存先との実際の契約整合性までは見えません。たとえば、APIレスポンスの形式が変わった、DBクエリ条件が実環境でずれる、通知基盤へ渡すフォーマットが違うといった不具合は、本物をつながない限り見逃しやすくなります。つまり、モック化は単体テストを強くする一方で、実連携の問題を別で見る必要があることを忘れてはいけません。単体テストが全部通っていても、本番に近い連携条件では壊れることは十分ありえます。
この点を見落とすと、「単体テストは全部通っているのに本番で連携が壊れる」という事態になりやすくなります。だからこそ、モック化は結合テストや疎通確認の代わりではなく、それらと役割分担して使うべきです。モック化の価値は、実連携確認を不要にすることではなく、確認の階層を整理することにあります。単体で見えるものと、本物をつないで初めて見えるものを切り分ける意識が大切です。
7. 実務で依存関係のモック化をどう使い分けるか
実務で依存関係のモック化をうまく使うには、どの依存を単体テストで置き換え、どの依存を結合テストで本物につなぐかを整理することが大切です。単体テストではロジックや分岐、例外処理を軽く安定して確認し、結合テストでは契約整合性や接続後の挙動を確認する、という役割分担が分かりやすいです。この切り分けがないと、単体テストが重くなりすぎたり、逆に実連携確認が抜けたりしやすくなります。つまり、モック化の上手さは「全部置き換えること」ではなく、「どこで置き換え、どこで本物を見るか」を切り分けられることにあります。
また、チーム内でこの判断基準が揃っていないと、人によって単体テストの重さやモック量がばらつきやすくなります。ある人は何でもモックにし、別の人はほとんど本物をつなぐ、という状態ではテスト全体の統一感が崩れます。そのため、個人の好みではなく、共通の考え方として整えることが重要です。テスト方針の共有は、コードスタイル以上に運用品質へ影響しやすいです。
7.1 単体テストで優先する場面
単体テストでモック化を優先したいのは、外部依存が重い、遅い、再現しづらい、副作用がある、あるいは今見たいものがロジック中心である場面です。たとえば、認証結果に応じた分岐、データ取得結果に応じた変換、通知条件の判定、例外時の振る舞いなどは、外部依存を置き換えたほうが意図を明確にしやすくなります。ここでは本物の依存の正しさより、「こういう条件を受け取ったときに対象がどう判断するか」のほうが重要です。つまり、単体テストでは「依存先そのもの」ではなく、「依存結果を受けた対象の振る舞い」を見ることを優先すべきです。
また、変更頻度が高い箇所ほど単体テストの速さと安定性が重要になります。日常的に直すロジックに対して毎回重い依存をつなぐのは効率が悪いため、こうした箇所ではモック化の価値が高くなります。小さな変更のたびに気軽に回せる状態を保てると、修正サイクルも短くなります。そのため、単体テストでは「今見たい責務に集中できるか」を基準にモック化を考えるのが自然です。
7.2 結合テストへ回すべき場面
一方で、実際の接続や契約整合性、設定差異、フォーマットの受け渡しといった点は、結合テストで見るべきです。モック化した単体テストだけでは、本物のAPI仕様変更やDBスキーマ差分、認証基盤との実連携不整合などは見えません。ここは本物をつないで初めて意味がある確認なので、単体テストで無理に担うべきではありません。つまり、実依存の本当の挙動を確認したい場面では、単体テストではなく結合テストへ回すべきです。
この切り分けができると、単体テストと結合テストの役割が重複しにくくなります。単体テストは軽く速く、結合テストは重要な接続だけを重点的に見る、という整理がしやすくなるからです。また、テストの種類ごとに期待する失敗理由も明確になります。役割分担が見えていると、モック化しすぎや本物依存しすぎも防ぎやすくなります。
単体テストで見る内容と結合テストで見る内容の整理
| 観点 | 単体テスト | 結合テスト |
|---|---|---|
| 主な確認対象 | 分岐、変換、例外処理、呼び出し条件 | 契約整合性、接続後の挙動、設定差 |
| 依存関係 | 置き換えることが多い | 本物をつなぐことが多い |
| 速度 | 速さ重視 | やや重くてもよい |
| 失敗要因 | ロジック中心 | 接続・連携も含む |
| 目的 | 小さな責務の確認 | 実際のつながりの確認 |
7.3 外部依存が重い処理での考え方
外部依存が重い処理では、単体テストでモック化する価値がとくに高くなります。決済、ストレージ、通知基盤、検索基盤などは、毎回本物をつなぐとコストも不安定さも増えやすいからです。こうした依存は、確認すべき失敗パターンも多く、本物だけで全部を見るのは現実的ではありません。こうした処理では、単体テストで業務判断を見て、必要最小限の結合テストで接続確認を行う、という分け方が現実的です。
また、外部依存が重いほど、単体テストへ引きずり込まないための設計も重要になります。依存境界を小さく保ち、中心ロジックから外部接点を切り離せるようにしておくと、モック化も結合確認もやりやすくなります。外部依存の重さをそのまま単体テストへ持ち込まないためには、設計段階での境界整理も欠かせません。テストと設計はここでかなり強く結びつきます。
7.4 変更頻度の高い箇所での考え方
変更頻度の高い箇所では、モック化によって単体テストを軽く保つ価値が高まります。少しずつ直しながら何度も回す必要があるからです。もし毎回重い依存を含むテストしかないと、開発テンポが落ち、結果としてテスト実行が後回しにされやすくなります。頻繁に変わる箇所ほど、「すぐ回せる」「すぐ結果が返る」という性質が重要になります。つまり、変わりやすい箇所ほど、依存を置き換えて短いフィードバックループを作ることが重要です。
ただし、変更頻度が高いからといって実連携を完全に見なくてよいわけではありません。重要な境界は別途結合テストで押さえる必要があります。そのため、速い単体テストと要所の結合テストをどう組み合わせるかが実務では大切になります。頻繁に変わるロジックほど、軽い確認と重い確認の分担を明確にしておいたほうが運用しやすくなります。
7.5 チームで基準を揃える進め方
依存関係のモック化を実務で安定して使うには、チームである程度基準を揃えることが重要です。どの依存は単体で置き換えるのか、どの確認は結合へ回すのか、呼び出し確認はどこまで許容するのか、時刻や乱数はどう扱うのかといった判断が人によって大きくぶれると、テスト全体の読みやすさや保守性もばらつきやすくなります。書き手ごとに前提が違うと、読む側も意図をつかみにくくなります。つまり、モック化の技法そのものより、運用基準の共有が大切です。
この基準は、厳密なルールブックでなくても構いません。レビューで共通言語を持てること、たとえば「単体ではロジック中心に見る」「副作用は呼び出し有無まで」「実契約は結合で確認する」といった方向性が共有されているだけでも大きく変わります。そうした基準があると、「なぜここをモックにしたのか」「なぜここは本物をつないでいるのか」を説明しやすくなります。チームとして揃った基準があると、モック化は個人技ではなく継続的なテスト文化として機能しやすくなります。
おわりに
依存関係のモック化とは、本物の外部依存をただ代用品へ置き換えることではなく、単体テストで確認したい責務へ集中しやすくするための設計と検証の考え方です。データベース、外部API、通知、時刻、環境情報などの依存を必要に応じて差し替えることで、テスト速度を安定させ、結果の再現性を高め、失敗原因を絞り込みやすくできます。その一方で、使いすぎると実装詳細へ寄りすぎた壊れやすいテストになりやすいため、何を単体で見て、何を結合で見るのかを意識した使い分けが重要です。モック化は強い道具ですが、だからこそ目的に応じて使う必要があります。
また、モック化しやすさはテスト技法の話だけではなく、依存を外から受け取り、具体実装へ直接つながず、入出力と業務ロジックを分けるといった設計のしやすさとも深くつながっています。だからこそ、依存関係のモック化をうまく扱えるようになると、テストしやすいコードだけでなく、差し替えやすく見通しのよい設計にも近づきやすくなります。実務で大切なのは、モック化を多用することではなく、テストの目的を守るために適切な場所で使うことです。そうした使い分けができるようになると、単体テストも結合テストもそれぞれの役割を果たしやすくなります。
EN
JP
KR