monkeypatch って何?mock とどう違うのか
pytest の monkeypatch は、
「テスト中だけ、モジュールやオブジェクトの属性を書き換えるための道具」です。
やりたいことは mock とほぼ同じで、
外部 API を呼ぶ関数を差し替えたい
現在時刻を固定したい
環境変数の値をテスト中だけ変えたい
といった「一時的なすり替え」です。
違いは、「pytest が用意しているフィクスチャとしての道具」だということ。unittest.mock.patch が「標準ライブラリの関数」であるのに対して、monkeypatch は「pytest がくれるオブジェクト」です。
感覚としては、
mock.patch
→ with 文やデコレータで「どこをどう差し替えるか」を書く
monkeypatch
→ テスト関数の中で「属性をこう書き換える」と命令する
というスタイルの違いです。
まずは一番シンプルな例:関数を差し替える
テストしたいコードを用意する
外部サービスからユーザー名を取ってくる関数を想像します。
# service.py
from external import get_user_name
def greet(user_id: int) -> str:
name = get_user_name(user_id)
return f"Hello, {name}!"
Pythonexternal.get_user_name は、本当は外部 API を叩くとします。
テストでは、そこを本物で動かしたくないので、
「テスト中だけニセの get_user_name にすり替えたい」という状況です。
monkeypatch で関数を差し替える
pytest のテストで、monkeypatch フィクスチャを引数に受け取ります。
# test_service.py
from service import greet
def test_greet_with_fake_user(monkeypatch):
def fake_get_user_name(user_id: int) -> str:
return "Taro"
monkeypatch.setattr("service.get_user_name", fake_get_user_name)
msg = greet(123)
assert msg == "Hello, Taro!"
Pythonここで起きていることを丁寧に分解します。
monkeypatch は pytest が自動で渡してくれる特別なオブジェクトmonkeypatch.setattr("service.get_user_name", fake_get_user_name) と書くと、
「service モジュールの中の get_user_name を、テスト中だけ fake_get_user_name に置き換える」
という命令になります。
重要なのは、"service.get_user_name" と「import している側」を指定していることです。
これは mock.patch と同じルールで、「使っている側の名前を差し替える」が正解です。
テストが終わると、pytest が自動で元に戻してくれます。with や finally を書かなくていいのが、monkeypatch の気持ちよさです。
monkeypatch と mock.patch の違いを感覚でつかむ
スタイルの違い
同じことを mock.patch で書くと、こうなります。
from unittest.mock import patch
from service import greet
def test_greet_with_fake_user():
def fake_get_user_name(user_id: int) -> str:
return "Taro"
with patch("service.get_user_name", fake_get_user_name):
msg = greet(123)
assert msg == "Hello, Taro!"
Pythonやっていることはほぼ同じですが、
mock.patch
→ with 文のブロックの中だけ差し替えが有効
monkeypatch
→ テスト関数の中で命令しておけば、そのテスト中はずっと有効(テスト終了時に自動で戻る)
という違いがあります。
pytest を前提にするなら、
「フィクスチャとして渡される monkeypatch を使う」方が自然なスタイルになりやすいです。
monkeypatch は「属性を書き換える道具」として割り切る
mock.patch は、MagicMock と組み合わせて
「呼び出し回数を検証する」「引数を検証する」などもよくやります。
monkeypatch は、もっとシンプルに、
この属性を、この値(関数・オブジェクト)に差し替える
この環境変数を、この値に変える
といった「書き換え」に特化した道具だと考えると分かりやすいです。
呼び出し回数などを検証したければ、
差し替える先に MagicMock を使えば OK です。
環境変数を monkeypatch でいじる(よくあるパターン)
テストしたいコード
例えば、環境変数から設定値を読む関数があるとします。
# config.py
import os
def get_api_key() -> str:
return os.environ["API_KEY"]
Pythonテストでは、「環境変数 API_KEY の値をテスト中だけ変えたい」ということがよくあります。
monkeypatch.setenv / monkeypatch.delenv を使う
# test_config.py
from config import get_api_key
def test_get_api_key(monkeypatch):
monkeypatch.setenv("API_KEY", "TEST-KEY-123")
assert get_api_key() == "TEST-KEY-123"
Pythonmonkeypatch.setenv("API_KEY", "TEST-KEY-123") と書くと、
テスト中だけ os.environ["API_KEY"] の値が "TEST-KEY-123" になる
テストが終わると、自動で元の値に戻る(なかった場合は削除される)
という挙動になります。
環境変数を直接いじると、
他のテストに影響が出たり、元に戻し忘れたりしがちですが、
monkeypatch を使えば「テストごとにクリーンな状態」を保ちやすくなります。
環境変数を消したいときは monkeypatch.delenv("API_KEY", raising=False) のように書けます。
現在時刻やランダムを固定する
テストしたいコード
例えば、「現在時刻を文字列にして返す」関数があるとします。
# clock.py
from datetime import datetime
def now_str() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
Pythonこのままだと、テストするたびに結果が変わってしまいます。
「テスト中だけ、datetime.now() の結果を固定したい」という場面です。
monkeypatch で datetime.now を差し替える
# test_clock.py
from datetime import datetime
from clock import now_str
def test_now_str(monkeypatch):
class DummyDateTime(datetime):
@classmethod
def now(cls, tz=None):
return cls(2024, 1, 2, 3, 4, 5)
monkeypatch.setattr("clock.datetime", DummyDateTime)
assert now_str() == "2024-01-02 03:04:05"
Pythonここでのポイントは、
clock.py の中では from datetime import datetime としている
つまり、clock モジュールの中では datetime.now() と呼んでいる
だから monkeypatch.setattr("clock.datetime", DummyDateTime) と書いて、clock モジュール内の datetime クラス自体を差し替えている
というところです。
これで、now_str() を呼んだときに、
常に「2024-01-02 03:04:05」が返るようになります。
現在時刻や乱数のような「毎回変わるもの」をテストするとき、
monkeypatch で「テスト中だけ固定する」のはよく使うテクニックです。
monkeypatch を使うときに意識してほしいポイント
「どの名前を差し替えるか」を正しく選ぶ
mock と同じく、monkeypatch でも一番ハマりやすいのはここです。
import している側のモジュール名.シンボル名を指定するfrom xxx import yyy なら、そのモジュールの中の yyy を差し替える
というルールを守ること。
例えば、
service.py で from external import get_user_name と書いているなら、monkeypatch.setattr("service.get_user_name", fake) が正解で、monkeypatch.setattr("external.get_user_name", fake) では効きません。
「テスト対象のコードの中で、どんな名前で呼ばれているか」を意識する癖をつけると、
monkeypatch も mock も一気に扱いやすくなります。
「テストごとに元に戻る」ことを前提に設計する
monkeypatch は、テストが終わると自動で元に戻してくれます。
これはつまり、
テストごとにクリーンな状態で始まる
他のテストに影響を残さない
という前提でテストを書ける、ということです。
逆に言うと、「グローバルな状態を書き換える」ようなコードは、
monkeypatch なしだとテストが壊れやすくなります。
グローバル変数
モジュールレベルの設定
環境変数
などをいじるコードは、
「テスト中だけ monkeypatch で差し替える」前提で設計しておくと、
テストの安定性がかなり上がります。
まとめ(monkeypatch は「pytest 流のすり替えツール」)
pytest の monkeypatch を初心者目線で整理すると、こうなります。
pytest がくれる monkeypatch フィクスチャを使って、モジュールやオブジェクトの属性を「テスト中だけ」別のものに差し替えられる。setattr で関数やクラスを差し替え、setenv / delenv で環境変数をいじれる。テスト終了時には自動で元に戻る。
mock.patch と同じく、「import している側の名前」を差し替えるのが重要なポイント。
外部 API、環境変数、現在時刻、乱数など「外部や変動するもの」をテストしやすくするための、pytest らしいシンプルな道具。

