Django テストで requests に Mock でパッチを当ててテストを実行する

Django テストで requests に Mock でパッチを当ててテストを実行します。

例えば、インターネットに出れない環境で外部 API をテストすると、接続エラーとなり、テストをすることができません。

そんな時は、Python の Mock モジュールを使います。Mock モジュールは、指定した関数を置き換えることができるので、そういったケースでもテストをすることができます。

また、API が開発中で利用できない場合でも、テストをすることができます。

バージョン

  • Python 3.7.3
  • Django 2.2.1
  • requests 2.22.0
  • beautifulsoup4 4.7.1

インストール

Python はすでにインストールされているものとします。

Python 仮想環境の作成

$ python3 -m venv venv
$ source venv/bin/activate

はじめに、Python の仮想環境を作成し、アクティベートします。

Django と requests のインストール

(venv) $ pip install django requests beautifulsoup4

Python の仮想環境を作成したら、そこに必要なパッケージをインストールします。

Django プロジェクトのインストール

(venv) $ django-admin startproject django_project
(venv) $ cd django_project/
(venv) $ python manage.py startapp external_api

Django のインストールが終わったら、django-admin コマンドを使って Django プロジェクトのインストールをします。

実装

settings.py


...

INSTALLED_APPS = [

    ...
    'external_api.apps.ExternalApiConfig'
]

...

今回の settings.py は、サンプルのテストを実行するための最低限の箇所だけにしています。

urls.py

from django.urls import path
from external_api.views import get_html

urlpatterns = [
    path('get-html/', get_html),
]

/get-html/ という URL を作成しました。この URL にアクセスすると、GET パラメータで指定した URL の HTML を取得するようにしたいと思います。

views.py

from django.http import HttpResponse

import requests

def get_html(request):
    url = request.GET.get('url')
    if url:
        res = requests.get(url)
        res.raise_for_status()
        return HttpResponse(res.content, content_type=res.headers.get('content-type', 'text/plain'))
    else:
        return HttpResponse('None')

/get-html/ の 処理です。GET パラメータ url で指定された URL の HTML を requests を使って取得し、その内容をレスポンスとして返します。

テスト

それでは、本題のテストを実装したいと思います。

tests.py

import requests

from bs4 import BeautifulSoup
from django.test import TestCase, Client
from unittest import mock

mock_res = requests.Response()
mock_res.status_code = 200
mock_res._content = "<title>Yahoo! JAPAN</title>"

class ExternalApiTest(TestCase):

    @mock.patch('external_api.views.requests.get', mock.MagicMock(return_value=mock_res))
    def test_get_html(self):
        url = 'http://yahoo.co.jp'
        res = Client().get('/get-html/?url=' + url)
        self.assertEqual(res.status_code, 200)
        soup = BeautifulSoup(res.content, 'html.parser')
        self.assertEqual(soup.title.string, 'Yahoo! JAPAN')

/get-html/ をテストします。GET パラメータ url は http://yahoo.co.jp を指定していますので、これを普通にテストすると、yahoo.co.jp にリクエストが行ってしまいます。冒頭でも言いましたが、インターネットに出れない環境でテストする場合は、エラーになりますので、mock.patch デコレータを使用します。

第一引数は、パッチを当てる関数の Python のパスになります。そして、第二引数の return_value に戻り値を渡します。

これで、インターネットに出れない環境でもテストすることができます。

動作確認

実装が全て終わったので、動作確認していきます。

テストの実行

(venv) $ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK
Destroying test database for alias 'default'...

manage.py の test コマンドを使って、Django のテストを実行します。

テストを実行すると、上記のように結果が OK になりました。インターネットに出れる環境であれば、mock.patch デコレータをコメントアウトして、テストを実行しても同じ結果になります。

まとめ

Django テストで requests に Mock でパッチを当ててテストを実行しました。

インターネットに出れない環境や開発中の API をテストする場合、Mock でパッチを当てることにより、指定した関数を置き換えることができるので、テストすることができました。

また、戻り値を好きに変えることができるので、様々なテストができるようになり、とても便利です。