pytest モック 使い方|外部API テスト を効率化する実践パターン5選

プログラミング

外部API の呼び出しをテストするとき、毎回実際のサーバーに接続するのは遅いし、課金も心配ですよね。

さらに、API の仕様が変わったり、接続が不安定になったりすると、テストが不安定になり、本当にコードに問題があるのか判断しづらくなります。

そんなときに活躍するのが pytest のモック機能 です。外部API の呼び出しを模擬して、確実で高速なテストを実現できます。

本記事では、実務で使えるモックのパターンを5つ紹介し、各パターンのコード例と使い分けポイントを解説します。

pytest モック とは:基本概念から理解する

モック(Mock)は、実際の外部API の代わりに、テスト用の模擬オブジェクトを用意する手法です。

pytest で使えるモックライブラリは複数ありますが、最も一般的なのは Python 標準の unittest.mock モジュール と、それを拡張した pytest-mock プラグインです。

モックを使うメリットは以下のとおりです。

  • テストが高速化される(ネットワーク遅延がない)
  • 外部API の課金が発生しない
  • API が変わってもテストコードは影響を受けない
  • ネットワーク障害の影響を受けない
  • 複雑なシナリオ(エラー応答など)を簡単に再現できる

実践パターン1:requests ライブラリを使った外部API 呼び出しのモック

最も基本的なパターンです。requests ライブラリで外部API を呼び出すコードをテストします。

テスト対象のコードは、以下のとおりです。

import requests

def get_user_data(user_id):
    """ユーザー情報を外部API から取得"""
    url = f"https://api.example.com/users/{user_id}"
    response = requests.get(url)
    response.raise_for_status()
    return response.json()

テストコードでは、requests.get をモックして、実際には API を呼び出さず、模擬データを返します。

import pytest
from unittest.mock import patch, MagicMock

def test_get_user_data(mocker):
    """requests.get をモックしてユーザー情報を取得"""
    mock_response = MagicMock()
    mock_response.json.return_value = {
        "id": 1,
        "name": "Taro Yamada",
        "email": "taro@example.com"
    }
    
    mocker.patch("requests.get", return_value=mock_response)
    
    result = get_user_data(1)
    
    assert result["name"] == "Taro Yamada"
    assert result["email"] == "taro@example.com"

ポイントは mocker.patch() でターゲットのモジュールを指定することです。

実践パターン2:複数の戻り値を返すシーケンシャルモック

API を複数回呼び出すテストでは、呼び出すたびに異なる結果を返す必要があります。

def get_multiple_users(user_ids):
    """複数のユーザー情報を順番に取得"""
    users = []
    for user_id in user_ids:
        user = get_user_data(user_id)
        users.append(user)
    return users

このコードをテストするとき、3回の API 呼び出しで異なるデータを返す設定が必要です。

def test_get_multiple_users(mocker):
    """複数呼び出しで異なるデータを返す"""
    mock_responses = [
        {"id": 1, "name": "Taro"},
        {"id": 2, "name": "Hanako"},
        {"id": 3, "name": "Jiro"}
    ]
    
    mock_response = MagicMock()
    mock_response.json.side_effect = mock_responses
    
    mocker.patch("requests.get", return_value=mock_response)
    
    result = get_multiple_users([1, 2, 3])
    
    assert len(result) == 3
    assert result[0]["name"] == "Taro"
    assert result[1]["name"] == "Hanako"
    assert result[2]["name"] == "Jiro"

side_effect を使うと、呼び出すたびに異なる値を返せます。

実践パターン3:API エラーレスポンスのモック

実際の運用では、API が エラーを返すこともあります。そのときのエラーハンドリングをテストする必要があります。

def get_user_data_safe(user_id):
    """エラーハンドリング付きでユーザー情報を取得"""
    try:
        url = f"https://api.example.com/users/{user_id}"
        response = requests.get(url, timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.HTTPError as e:
        print(f"HTTP エラー: {e.response.status_code}")
        return None
    except requests.exceptions.RequestException as e:
        print(f"リクエストエラー: {e}")
        return None

404 やタイムアウトの場合をテストします。

import requests

def test_get_user_data_not_found(mocker):
    """404 エラーの場合をテスト"""
    mock_response = MagicMock()
    mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found")
    
    mocker.patch("requests.get", return_value=mock_response)
    
    result = get_user_data_safe(999)
    
    assert result is None

def test_get_user_data_timeout(mocker):
    """タイムアウトの場合をテスト"""
    mocker.patch(
        "requests.get",
        side_effect=requests.exceptions.Timeout("Connection timeout")
    )
    
    result = get_user_data_safe(1)
    
    assert result is None

こうすることで、エラーケースも安全にテストできます。

実践パターン4:call_args_list で API 呼び出しの検証

モックは単に戻り値を返すだけでなく、API が正しく呼ばれたかを検証 することも重要です。

def test_verify_api_call(mocker):
    """正しいパラメータで API が呼ばれたか検証"""
    mock_get = mocker.patch("requests.get")
    mock_get.return_value.json.return_value = {"id": 1}
    
    get_user_data(1)
    
    # API が正しいURL で呼ばれたか確認
    mock_get.assert_called_once_with("https://api.example.com/users/1")
    
    # 呼び出し回数の検証
    assert mock_get.call_count == 1
    
    # 呼び出し時の引数の検証
    call_args = mock_get.call_args
    assert "api.example.com" in call_args[0][0]

これにより、コードが期待どおりに API を呼び出しているかを確認できます。

実践パターン5:fixture を使った再利用可能なモック設定

複数のテストで同じモック設定を使う場合、pytest の fixture を活用 すると DRY 原則を守れます。

import pytest
from unittest.mock import MagicMock

@pytest.fixture
def mock_api_response(mocker):
    """API レスポンスのモック fixture"""
    mock_response = MagicMock()
    mock_response.json.return_value = {
        "id": 1,
        "name": "Test User",
        "email": "test@example.com"
    }
    
    mocker.patch("requests.get", return_value=mock_response)
    
    return mock_response

@pytest.fixture
def mock_api_error(mocker):
    """API エラーのモック fixture"""
    mocker.patch(
        "requests.get",
        side_effect=requests.exceptions.HTTPError("500 Server Error")
    )

def test_with_success(mock_api_response):
    """成功ケースのテスト"""
    result = get_user_data(1)
    assert result["name"] == "Test User"

def test_with_error(mock_api_error):
    """エラーケースのテスト"""
    result = get_user_data_safe(1)
    assert result is None

fixture を使うと、テストコードがシンプルになり、保守もしやすくなります。

pytest モック と実装パターンの比較表

パターン 使用場面 難易度 再利用性
基本的なパッチ 単純な API 呼び出し
side_effect 複数回の異なる戻り値
例外エラー エラーハンドリングのテスト
call_args_list 検証 API 呼び出しの確認
fixture 複数テスト間の共通設定

実践での注意点と Tips

モック機能は便利ですが、いくつかの注意点があります。

モック範囲を意識することが大切です。モックの対象を間違えると、テストが通っても実際には動かない可能性があります。

たとえば、requests.get をモックするときは、対象のモジュール内での import 方法に注意が必要です。

# my_module.py 内で
from requests import get  # この場合は my_module.get をモックする

# または
import requests           # この場合は requests.get をモックする

テスト環境と本番環境を分離する のも重要です。製造業の中小企業がAIで品質管理を自動化 する場合も、AI システムと外部API の連携テストでは、モックを活用して安全にテストします。

モック側とコード側の整合性を保つ ことも忘れずに。API の仕様が変わったら、モックの設定も更新する必要があります。

実際のプロジェクトへの応用

これらのモック技法は、単なる単体テストにとどまりません。

中小企業のChatGPT導入完全ガイド では、外部の LLM API を呼び出すコードのテストでも同じ手法が活躍します。

ChatGPT API や他の AI サービスの呼び出しをモックすることで、開発中の課金を避けながら、堅牢なテストを書けます。

さらに、個人事業主がChatGPTで売上を40%増やした実例 のように、複数の外部API を組み合わせるとき、各API をモックして組み合わせテストを行えば、統合の問題を早期に発見できます。

CI/CD パイプラインに pytest を統合する場合も、モックの活用は必須です。パイプライン内では外部サービスへのアクセスが制限されることが多いため、モックがあれば自動テストを確実に実行できます。

おすすめ書籍・ガジェット

  • pytest入門ガイド:pytest の基本から応用までを体系的に学べる、実務向けの教科書です。モック機能の詳しい解説も充実しています。
  • Python単体テスト実践:unittest.mock の詳細と、テスト駆動開発(TDD)の考え方を学べる良書です。
  • HHKB Professional:プログラミングの長時間作業に最適なキーボード。快適なコーディング環境は、テストコードの品質向上にもつながります。

まとめ:pytest モック を活用した効率的なテスト

pytest のモック機能を正しく使えば、外部API に依存しない、高速で信頼性の高いテストが実現できます。

5つのパターンを紹介しましたが、最初は基本的なパッチから始めて、プロジェクトに応じて複雑なパターンに進んでいくのがおすすめです。

特に、現代のソフトウェア開発では、複数の外部API を組み合わせることが当たり前になっています。中小企業のホームページをAI集客で自動化する実践チュートリアル のように、複合的なシステムを構築する場合、モックの活用が不可欠です。

本記事のコード例を参考に、実プロジェクトでモック機能を活用してください。テストの品質が向上すれば、本番環境でのバグは大幅に減り、開発スピードも上がります。

pytest のモック と unittest.mock の違いは何ですか?

pytest 自体にはモック機能がなく、pytest-mock プラグインを使って unittest.mock を拡張しています。pytest-mock は pytest の fixture 機能と統合されており、より簡潔で読みやすいテストコードが書けます。具体的には、mocker という fixture を直接使えるため、import や setup コードが減り、テストに集中できます。

モックを使うとなぜテストが高速化されるのですか?

外部API の呼び出しには通常、ネットワークのラウンドトリップ時間(数百ミリ秒〜数秒)が発生します。モックを使えば、この遅延が完全に排除され、ネットワークアクセスなしに瞬時に結果が返されます。数百個のテストケースがある場合、全体の実行時間が数分から数秒に短縮されることもあります。

モックをした場合、実際の API との連携は確認できないのではないですか?

その通りです。そのため、単体テスト(ユニットテスト)ではモックを使い、統合テスト(インテグレーションテスト)では実際の API またはステージング環境と連携テストを行うのが一般的です。異なるテスト段階で異なるテスト方法を使い分けることで、開発効率と品質の両立が実現できます。

side_effect と return_value の使い分けはどのようにしますか?

return_value は、モックが呼ばれるたびに同じ値を返します。一方、side_effect はリストやイテレータを指定すると、呼ばれるたびに異なる値を返すか、例外を発生させます。複数回の呼び出しで異なる結果が必要な場合や、エラーを模擬したい場合は side_effect を使用します。

CI/CD パイプラインでモックを使う際の注意点は?

CI/CD パイプライン内のテストではネットワークアクセスが制限されることが多いため、モックを積極的に使うべきです。ただし、定期的(夜間など)に実際の API との統合テストを別途実行し、本当に API と連携できるか確認することが重要です。モック単体では、API 仕様の変更に気付けないリスクがあります。

関連記事・さらに学習を深めるなら

外部API のテストについてさらに学びたい場合、ローカルLLM Ollamaの使い方 の記事では、ローカル環境で LLM を動かす方法を解説しており、外部サービスに依存しないテスト環境の構築方法も参考になります。

さらに、プロジェクト内で複数のエンジニアが協力して開発する場合、テストの統一性を保つことが重要です。本記事のパターンをチームのコーディング規約に取り込むことで、保守性の高いテストコードが実現できます。

pytest のモック機能は、Python での単体テストの鍵となるスキルです。ぜひ実務で活用してください。

🤖 このブログはAIで自動運営しています。 同じ仕組みを御社にも導入できます。 無料相談はこちら
タイトルとURLをコピーしました