Django Ninjaテストコード実践

こんにちは。
グローバルウェイの清家です。
Django Ninjaでテストを書くための”実践向けガイド”を紹介します。
Django Ninjaでは、URLリゾルバやミドルウェアの層を介さず高速にAPIのみをテストできる専用のテストクライアントを提供しています。
※Django Ninjaでのテストコードの実装は、過去の記事で紹介しています。
この記事は以下のターゲットを対象としています。
★5 Djangoの開発経験が3年以上。
★4 Djangoの開発経験が1年以上。
★3 Webサイト開発経験あり。これからDjangoを学習します。
★2 Python初級者。簡単なプログラムコードが書けます。
★1プログラミング未経験。
目次
なぜDjango Ninja専用のテストクライアントを使うのか
- 処理経路
Django NinjaのRouter/APIを直接呼び出す設計で、URLリゾルバやミドルウェアを介さないため、軽量で高速、APIロジックに集中してテストを行えます。 - ミドルウェア
CSRFやミドルウェアの副作用に影響されません。
代わりに、リクエストヘッダーやクッキーはテストクライアントのインスタンス化する際やget、post関数の引数に直接指定することができます。 - レスポンス/データアクセス
response.json()によるJSON形式のデータ取得に加え、response.data(Schemaでデシリアライズ)が利用可能で、型安全に近いアサーションが書けます。 - 非同期(async)ビューのテスト
非同期用テストクライアント(TestAsyncClient)を提供します。
<DjangoとDjango NinjaのTestClientの比較>
| 項目 | Django テストクライアント django.test.Client | Django Ninja テストクライアント ninja.testing.TestClient |
| 目的 | Djangoアプリ全体の統合テスト | APIロジックのユニットテスト |
| 処理経路 | URLconfからミドルウェアを経由してテンプレートレンダリング | URLリゾルバやミドルウェアを介さない |
| ミドルウェア | ミドルウェアに依存する挙動 | ミドルウェアをスキップする |
| レスポンス/データアクセス | response.status_code response.contentなどに加えテンプレートやリダイレクトの検証が可能 | response.json() response.data |
| 非同期(async)ビューのテスト | 非対応(独自カスタマイズが必要) | TestAsyncClientを提供する |
次項では、テストコードの使用方法について紹介します。
テストコードを動かしてみよう
過去の記事:[Django Ninjaを使ってみる]で紹介したコードを、テストします。
本サンプルでは、正常動作(HTTPステータス200)とMethod Not Allowed(HTTPステータス405)を作成します。
〔api/tests.py〕
from django.test import TestCase
from ninja.testing import TestClient
from api.views import router
class HelloAPITestCaseWithNinjaTestClient(TestCase):
"""
Django NinjaのTestClientを使用した /v1/hello/ エンドポイントのテスト
"""
def setUp(self):
# """テストの前準備"""
self.client = TestClient(router)
def test_hello_endpoint_success(self):
"""GET /v1/hello/ が正常に動作することを確認"""
response = self.client.get("/hello/")
# ステータスコードが200であることを確認
self.assertEqual(response.status_code, 200)
# レスポンス内容が"Hello World!"であることを確認
self.assertEqual(response.json(), "Hello World!")
def test_hello_endpoint_method_not_allowed(self):
"""POSTメソッドが許可されていないことを確認"""
response = self.client.post("/hello/")
# 405 Method Not Allowedが返されることを確認
self.assertEqual(response.status_code, 405)
ミドルウェアを作成します。
本サンプルコードでは、リクエストログをコンソールに出力します。
import logging
import time
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger(__name__)
class RequestLoggingMiddleware(MiddlewareMixin):
"""
リクエストのログ記録を行うミドルウェア
"""
def process_request(self, request):
"""リクエスト開始時に実行"""
request.start_time = time.time()
return None
def process_response(self, request, response):
"""レスポンス送信前に実行"""
if hasattr(request, "start_time"):
duration = time.time() - request.start_time
logger.info(
f"{request.method} {request.path} - "
f"Status: {response.status_code} - "
f"Duration: {duration:.3f}s"
)
return response
テストコードを実行します。
ミドルウェアをスキップして、APIのみがテストされていることがわかります。
〔ターミナル〕
$ python3.12 manage.py test api.tests.HelloAPITestCaseWithNinjaTestClient
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 2 tests in 0.021s
OK
Destroying test database for alias 'default'...
アプリ全体の振る舞いまで含めて検証したい
URLリゾルバやミドルウェアなどアプリ全体の振る舞いまで含めて動作検証したい場合、Django標準のTestClientを利用します。
〔api/tests.py〕
from django.test import Client, TestCase
from django.urls import reverse
class HelloAPITestCase(TestCase):
"""
/v1/hello/ エンドポイントのテスト
"""
def setUp(self):
"""テストの前準備"""
self.client = Client()
def test_hello_endpoint_success(self):
"""GET /v1/hello/ が正常に動作することを確認"""
response = self.client.get("/v1/hello/")
# ステータスコードが200であることを確認
self.assertEqual(response.status_code, 200)
# レスポンス内容が"Hello World!"であることを確認
self.assertEqual(response.json(), "Hello World!")
def test_hello_endpoint_method_not_allowed(self):
"""POSTメソッドが許可されていないことを確認"""
response = self.client.post("/v1/hello/")
# 405 Method Not Allowedが返されることを確認
self.assertEqual(response.status_code, 405)
テストコードを実行します。
ミドルウェアによるログ出力が確認できます。
〔ターミナル〕
$ python3.12 manage.py test api.tests.HelloAPITestCase
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
INFO 2026-01-01 04:21:35,349 middleware GET /v1/hello/ - Status: 200 - Duration: 0.001s
.INFO 2026-01-01 04:21:35,371 middleware POST /v1/hello/ - Status: 405 - Duration: 0.000s
WARNING 2026-01-01 04:21:35,392 log Method Not Allowed: /v1/hello/
.
----------------------------------------------------------------------
Ran 2 tests in 0.110s
OK
Destroying test database for alias 'default'...
まとめ
Django Ninjaによるテストクライアントを使ったテストを紹介しました。
もし「ミドルウェアの動作も含めてNinja APIを検証したい」ケースがあれば、テストの目的に応じて両テストクライアントを使い分けるのがおすすめです。
なお、こうした話題も含め松山市で定期的にPythonセミナー等の活動を行っております。ご関心いただければ幸います。

