はじめに
ユニットテストにおいて、外部依存性(データベース、API、ファイルシステムなど)の分離は重要な課題です。
Python の unittest.mock
は、このような依存性を効果的にモック化するための強力なツールを提供します。
この記事では、外部 API を呼び出すクラスのテストを例に、unittest.mock
の実践的な使い方を詳しく解説します。
テストターゲット
次のような処理でユーザー情報を取得するクラスのテストを実装することを考えます。
import requests
class UserService:
def get_user_data(self, user_id):
response = requests.get(f"https://example.com/users/{user_id}")
return response.json()
※ 以前紹介した Requests ライブラリを使用してみました
テストの課題
対象の API が叩き放題であれば、おそらく下記の記事に沿ってテストを実装すれば実現は可能です。
しかし通常は下記のような課題があります。
- テストの実施にネットワーク接続が必要になる
- API の応答が遅いため、テストに時間がかかる
- API によっては、API コール時に課金される可能性がある
- テストの安定性と再現性の低下が懸念される
API の呼び出し部分はモック化し実際には呼び出さないようにして、API の仕様に沿ってダミーのレスポンスを返すようにできないでしょうか ?
モックテスト
unittest.Mock
を使用することで、API を呼び出している requests.get
の振舞いを Mock に置き換えることができます。
下記のコードをご確認ください。
import unittest
from unittest.mock import Mock, patch
class TestUserService(unittest.TestCase):
@patch("requests.get")
def test_get_user_data(self, mock_get):
# モックのレスポンスを設定
mock_response = Mock()
mock_response.json.return_value = {
"id": 1,
"name": "Test User",
"email": "test@example.com",
}
mock_get.return_value = mock_response
# テスト実行
service = UserService()
result = service.get_user_data(1)
# 検証
self.assertEqual(result["name"], "Test User")
# APIコール検証
mock_get.assert_called_once_with("https://example.com/users/1")
@patch
デコレータの詳細
@patch('requests.get')
は、テストメソッド内で requests.get
メソッドをモックに置き換えます。
主な特徴:
- テストスコープ内でのみモックが有効
- テストメソッドの第 2 引数としてモックオブジェクトが渡される
- テストメソッド終了後に自動的に元の関数に戻る
return_value
の活用
置き換えたメソッドの振舞いは、test_get_user_data
メソッドの第 2 引数 mock_get
を変更することでエミュレートできます。
今回の場合、return_value
にダミーの値を設定することで、get
メソッドの戻り値をテスト用のものに加工しています。
エラーケースのテスト
エラーを擬似的に発生させるには、Mock に side_effect
を設定します。
下記のように記載することで、requests.get
の実行時に例外を発生させられます。
@patch("requests.get")
def test_api_error_handling(self, mock_get):
# HTTPエラーをシミュレート
mock_get.side_effect = requests.exceptions.HTTPError("Not Found")
service = UserService()
with self.assertRaises(requests.exceptions.HTTPError):
service.get_user_data(999)
まとめ
unittest.mock
を使用することで、下記が実現できます。
- 外部依存性からテストを分離
- 高速で安定したユニットテストの実装
今回は一部の例のみをご紹介しましたが、入門サイトを見ると様々なケースへの対応方法が記載されていますので、一度確認してみてはいかがでしょうか ?