Pythonでテストを書いていると、次のような悩みに直面することがあります。
- 外部APIを呼び出す処理があり、テストのたびに通信するのは避けたい
- DB接続の準備が必要になり、テストが重くなる
- テスト対象の関数の中で別クラスや別関数が呼ばれていて、思った通りにテストできない
「この処理のロジックだけ確かめたいのに、なぜ余計な準備が必要なのだろう」
そう感じる瞬間は多いです。テストは負担を増やすものではなく、安心して開発するための仕組みであるべきです。
そこで役立つのが MagicMock です。本記事では、MagicMockとは何か、どのように使うとテストが楽になるのかを、実務レベルのコード例を交えてわかりやすく解説します。
1. MagicMockとは何か?
MagicMock は Python 標準ライブラリ unittest.mock に含まれる、偽のオブジェクト(モック)を作成する仕組みです。
偽物とはいえ、次のような特徴があります。
- 既存のクラスや関数の代わりに使用できる
- 戻り値を自由に設定できる
- 呼び出し回数や引数を検証できる
つまり、外部に依存したくないテストを、純粋にロジックだけ確認できる状態に変えてくれる道具です。
テストを書くうえでの本音はこうです。
「DBやAPIの動作ではなく、テスト対象の関数が正しい結果を返しているかだけ知りたい」
MagicMockはその気持ちを叶えてくれます。
2. MagicMockの最小例(依存クラス置き換えパターン)
まずは、MagicMockの基本的な使い方を見ていきます。
実装コード
# case1/main.py
class ApiClient:
def __init__(self, user_id: str):
self.user_id = user_id
def get_user(self, user_id):
res = self.user_id if self.user_id == user_id else "error"
return res
def fetch_user(api_client: ApiClient, user_id: str) -> str:
return "user_id:" + str(api_client.get_user(user_id))
fetch_user() をテストしたいだけなのに、ApiClient の実装まで準備したくない場合があります。
本来確認したいのは fetch_user の戻り値だけであり、ApiClient の動作そのものではありません。
MagicMockで依存を無効化する
# case1/test_fetch_user.py
from unittest.mock import MagicMock
from case1.main import fetch_user
def test_fetch_user():
mock_client = MagicMock()
mock_client.get_user.return_value = "magic_mock_return_value"
result = fetch_user(mock_client, "magic_mock_return_value")
mock_client.get_user.assert_called_once_with("magic_mock_return_value")
assert result == "user_id:magic_mock_return_value"
このテストが成立する理由
| 観点 | MagicMockの役割 |
|---|---|
| 本物のクラス | 不要 |
| 呼び出すメソッド | MagicMockが代替 |
| 戻り値 | return_valueで指定可能 |
| 呼び出し検証 | assert_called_once_withで確認 |
MagicMockを渡すだけで、依存クラスが存在するように“見せかける”環境が完成します。
3. しかし、MagicMockだけでは解決できないケースがある
ここまで見ると、「MagicMockさえあれば全部解決できるのでは?」と思うかもしれません。
しかし、実務ではそう簡単ではないことがあります。
次のコードを見てください。
# case2/email.py
def send_email(to: str, body: str) -> bool:
"""本物は外部のメールサーバに送る、というイメージ"""
print(f"Send mail to {to}: {body}")
# 実際には失敗することもあるが、ここではTrueで決め打ち
return True
# case2/notify.py
from case2.email import send_email
def notify_user(user_id: int, message: str) -> bool:
email = f"user{user_id}@example.com"
return send_email(email, message)
この関数には問題があります。
-
send_emailを引数で受け取っていない
→ MagicMockを外から渡す余地がない
つまり、MagicMock単体では置き換えられません。
ここで必要になるのが patch です。
4. なぜ patch が必要なのか?
MagicMock と patch の役割の違い
| 名前 | 何をする? | 正体 |
|---|---|---|
| MagicMock | 偽物の関数やクラスを作る | モック本体 |
| patch | その偽物を指定した場所に差し込む | 差し替え装置 |
MagicMockは「部品」
patchは「部品交換に使う工具」
というイメージです。
5. patchを使ったテスト例
from unittest.mock import patch
from case2.notify import notify_user
def test_notify_user_with_patch():
with patch("case2.notify.send_email") as mock_send_email:
mock_send_email.return_value = True
result = notify_user(1, "Test Message")
mock_send_email.assert_called_once_with("user1@example.com", "Test Message")
assert result is True
ここで起きていること
-
case2.notify.send_emailの呼び出し先が MagicMock に置き換わる - 本物のメール送信は実行されない
- 呼び出し履歴を検証できる
→ MagicMock単体では不可能な状況を patch が解決している
"case2.email.send_email"ではないのか?と思った方は鋭いです。
patchを使う場合、テストしたい関数に視点をおくことが重要になります。
今回テストしたい関数は case2/notify.pyのnotify_userであり、その中で呼び出しているsend_email関数をダミーに置換したいのでした。
ここでの視点はあくまでもcase2/notify.pyです。
したがって、case2/notify.pyから見ると、send_emailはcase2.notify.send_emailという名前空間にあるため、patch()は、"case2.notify.send_email"が正しいのです。
6. 外部APIやLLM呼び出しにも応用できる
外部サービスと通信する関数もテストしやすくなります。
# case3/call_llm.py
import os
from litellm import completion
api_key = os.environ.get("GEMINI_API_KEY")
def call_gemini():
response = completion(
model="gemini/gemini-2.5-flash",
api_key=api_key,
messages=[{"role": "user", "content": "こんにちは!Geminiで何ができますか?"}],
)
content = response.choices[0].message.content
return content
def main():
content = call_gemini()
print(f"{content=}")
if __name__ == "__main__":
main()
from unittest.mock import patch
from case3.call_llm import main
def test_case3_main_with_patch():
with patch("case3.call_llm.call_gemini") as mock_call_gemini:
mock_call_gemini.return_value = "モックされた応答"
main()
mock_call_gemini.assert_called_once()
APIの速度やコストに左右されなくなり、テストの安定性が向上します。
7. MagicMockを使うべき場面
| 状況 | MagicMockで解決可能? | 理由 |
|---|---|---|
| 外部サービスやDBに依存 | ◎ | テストが遅く不安定になる |
| 返り値だけ変えたい | ◎ | MagicMock単体で対応できる |
| 呼び出し履歴を確認したい | ◎ | assert系が活躍 |
| 関数内部の依存関数を差し替えたい | △ | patchが必要になる |
8. まとめ
MagicMockは、テストを書くときの次の悩みを解消してくれます。
- 外部依存を切り離してロジックだけ確認できる
- テストが速く・安定する
- 呼び出し履歴まで検証できる
- 余計なセットアップが不要になる
そして、MagicMockだけでは置き換えられないケースに直面したとき、
その隙間を埋めてくれるのが patch です。
MagicMockとpatchを理解して使い分けられるようになると、テストは一気に楽になります。
テストが負担ではなく、安心して開発を進めるための武器になるはずです。