mock って何?まずはイメージから
mock(モック)は、テストのときだけ「本物の代わりに振る舞うニセモノのオブジェクト」です。
本物をそのまま使うと困る場面を想像してみてください。
外部 API を叩く(お金がかかる・遅い・回数制限がある)
メールを送る(本当に送られたら困る)
DB に書き込む(テストのたびにデータが増える)
現在時刻や乱数に依存する(結果が毎回変わる)
こういう「テストのたびに本物を動かしたくない」「制御できない」ものを、
テスト中だけ「ニセモノ」にすり替えてしまうのが mock です。
mock を使うと、
呼ばれたかどうか
何回呼ばれたか
どんな引数で呼ばれたか
どんな値を返すことにするか
を、テスト側から自由にコントロールできます。
まずは「外部 API を呼ぶ関数」を例にする
テストしたい関数(本物のままだと困るやつ)
例えば、requests を使って外部 API からユーザー情報を取ってくる関数があるとします。
# api_client.py
import requests
def fetch_user_name(user_id: int) -> str:
url = f"https://example.com/api/users/{user_id}"
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
return data["name"]
Pythonこの関数をテストしたいとき、
本当に https://example.com にリクエストを飛ばしたくはないですよね。
ネットワーク環境に依存する
API 側の状態に依存する
テストを何度も回すと迷惑になる
など、テストが「遅い・不安定・迷惑」になりがちです。
ここで mock の出番です。
unittest.mock.patch の基本(関数をニセモノにすり替える)
requests.get を mock に置き換える
Python 標準ライブラリの unittest.mock を使います。
pytest でもそのまま使えます。
# test_api_client.py
from unittest.mock import patch, MagicMock
from api_client import fetch_user_name
def test_fetch_user_name():
fake_response = MagicMock()
fake_response.json.return_value = {"name": "Taro"}
fake_response.raise_for_status.return_value = None
with patch("api_client.requests.get", return_value=fake_response) as mock_get:
name = fetch_user_name(123)
assert name == "Taro"
mock_get.assert_called_once_with("https://example.com/api/users/123", timeout=5)
Pythonここで起きていることを丁寧に分解します。
patch("api_client.requests.get", ...)api_client モジュールの中で使われている requests.get を、テスト中だけ差し替える宣言です。
ポイントは「import している側の名前」を指定することです(ここは重要なので後で深掘りします)。
return_value=fake_responsefetch_user_name の中で requests.get(...) が呼ばれたとき、
本物の HTTP リクエストは飛ばずに、代わりに fake_response が返されます。
fake_response.json.return_value = {"name": "Taro"}fake_response.json() が呼ばれたら、この辞書を返すように設定しています。
fake_response.raise_for_status.return_value = Noneraise_for_status() が呼ばれても何も起きない(例外を投げない)ようにしています。
mock_get.assert_called_once_with(...)
「どんな URL と引数で呼ばれたか」を検証しています。
これにより、「正しい URL を組み立てているか」もテストできます。
つまり、テストでは、
HTTP 通信は一切していない
でも「通信したかのように」振る舞わせている
その上で「返り値」と「呼び出し方」を検証している
という状態になっています。
「どこを patch するか」が超重要なポイント
import している側を patch する、というルール
さっきの例で patch("api_client.requests.get", ...) と書いたのは、
かなり重要なポイントです。
api_client.py の中では、こう書いていました。
import requests
def fetch_user_name(...):
resp = requests.get(...)
Pythonつまり、fetch_user_name の中から見える名前は requests.get です。
この「見えている名前」を差し替える必要があります。
もしここで、
with patch("requests.get", ...) # こう書くと…
Pythonと書いてしまうと、api_client モジュールの中の requests.get は差し替わりません。api_client はすでに import requests 済みだからです。
mock で一番ハマりやすいのがここです。
「import している側のモジュール名.シンボル名を patch する」
というルールを、早めに体に染み込ませておくと、後々かなり楽になります。
MagicMock の基本的な振る舞いを押さえる
何をしてもとりあえず動く「何でも屋」
MagicMock は、「何をしてもとりあえずそれっぽく動く」オブジェクトです。
from unittest.mock import MagicMock
m = MagicMock()
m.foo() # エラーにならない
m.bar(1, 2, 3) # これもエラーにならない
print(m.baz) # 属性アクセスもできる
Pythonデフォルトでは、
どんなメソッドを呼んでも、どんな属性を触っても、
新しい MagicMock が返ってきます。
だからこそ、テストで使うときは、
return_valueside_effect属性名.return_value
などを使って、「どう振る舞ってほしいか」を明示的に設定します。
呼び出し履歴を検証できる
MagicMock は、「どう呼ばれたか」の情報も覚えています。
m = MagicMock()
m.foo(1, 2, x=3)
m.foo.assert_called_once_with(1, 2, x=3)
Pythonこれにより、
この関数が呼ばれたか?
何回呼ばれたか?
どんな引数で呼ばれたか?
をテストで検証できます。
mock の本質は、「外部とのやりとり」をテストすることです。
返り値だけでなく、「呼び出し方」もテスト対象になる、という感覚を持っておくと強いです。
副作用のある処理を mock で封じる
メール送信を例にする
例えば、ユーザー登録時にメールを送る関数があるとします。
# notifier.py
def send_welcome_email(email: str) -> None:
print(f"Send email to {email}") # 本当は外部サービスを叩く想定
# service.py
from notifier import send_welcome_email
def register_user(email: str) -> None:
# ここで DB にユーザーを作る想定
send_welcome_email(email)
Pythonregister_user をテストするとき、
本当にメールを送る必要はありません。
「メール送信関数が呼ばれたかどうか」だけ分かれば十分です。
# test_service.py
from unittest.mock import patch
from service import register_user
def test_register_user_sends_email():
with patch("service.send_welcome_email") as mock_send:
register_user("taro@example.com")
mock_send.assert_called_once_with("taro@example.com")
Pythonここでは、
service モジュールの中で使われている send_welcome_email を差し替える
実際には何も送らない(中身は空の mock)
「呼ばれたかどうか」「どんな引数で呼ばれたか」だけを検証する
という形になっています。
これが mock の典型的な使い方です。
mock を使うときに意識してほしい設計の感覚
「外部との境界」を意識する
mock が必要になるのは、だいたい「外部との境界」です。
ネットワーク(HTTP、gRPC など)
ファイルシステム
DB
メール・キュー・メッセージング
現在時刻・乱数・環境変数
こういった「外の世界」に触る部分を、
コードの中でちゃんと分離しておくと、
そこだけを mock で差し替えやすくなります。
逆に、ビジネスロジックの中に直接 requests.get や open が散らばっていると、
テストで差し替えるのが大変になります。
「外部とのやりとりを行う関数(やクラス)」と
「ビジネスロジックの本体」を分ける設計は、
テストしやすさの面でも非常に重要です。
「何でもかんでも mock」は逆効果
mock は便利ですが、乱用するとテストが壊れやすくなります。
内部実装の細かい呼び出し方に依存しすぎる
実装を少し変えただけでテストが大量に壊れる
「何を保証したいテストなのか」が分からなくなる
という状態になりがちです。
mock を使うときは、
本当に外部とのやりとりを隠したいのか?
返り値だけでなく、「呼び出しが行われたこと」自体が仕様なのか?
を一度立ち止まって考えるといいです。
「純粋な計算ロジック」は、mock なしでテストできるようにしておくのが理想です。
まとめ(mock は「外の世界のニセモノ」を作る道具)
Python/pytest における mock を、初心者目線で整理するとこうなります。
本物を動かしたくない処理(外部 API、メール、DB、時刻など)を、テスト中だけニセモノにすり替える仕組みが mock。unittest.mock.patch で「import している側の名前」を差し替え、MagicMock で返り値や呼び出し履歴をコントロールする。
返り値だけでなく、「何回・どんな引数で呼ばれたか」を検証できるので、「外部とのやりとり」を安全にテストできる。
mock を使う前提として、「外部との境界」と「ビジネスロジック」を分けた設計にしておくと、テストがシンプルになる。
