【Django】JsonResponseでのテストの仕方(GET/POST)
久々の投稿となってしまいました(^^;
今回使用したバージョン
* Python 3.7.1 * Django 2.1.7
今回サンプルとしてテストしたいメソッドはこちらのView関数
views.py
import json from django.views.decorators.http import require_POST, require_GET from django.http import JsonResponse from .models import Item @require_GET def get_item(request, id: int) -> JsonResponse: """商品情報を取得するAPIです。 :param request: :param id: ItemモデルのPK :return: JsonResponse """ try: item = Item.objects.get(pk=id) except Item.DoesNotExist: return JsonResponse( data={}, status=404 ) data = { 'name': item.name, 'price': item.price, 'type': item.get_type_display() } return JsonResponse( data=data ) @require_POST def create_item(request) -> JsonResponse: """商品情報を登録するAPIです。 :param request: :return: """ request_data = json.loads(request.body) Item.objects.create( name=request_data.get('name'), price=request_data.get('price'), type=request_data.get('type'), other_type=request_data.get('other_type') ) data = { 'message': '商品を登録しました' } return JsonResponse( data=data )
付随する情報たち
urls.py
from django.urls import path from . import views app_name = 'items' urlpatterns = [ path('<int:id>', views.get_item, name='get'), path('create', views.create_item, name='create'), ]
models.py
from django.db import models from django.core.validators import MinValueValidator class Item(models.Model): """商品モデル """ # 品種 TYPE_CHOICES = ( (0, 'コーヒー'), (1, '紅茶'), (2, 'ココア'), (3, 'タピオカ'), (4, 'その他') ) name = models.CharField(max_length=225) price = models.IntegerField(validators=[MinValueValidator(0)]) type = models.SmallIntegerField(choices=TYPE_CHOICES) other_type = models.CharField(max_length=225, blank=True, null=True) # typeがその他の場合 class Meta: db_table = 'items'
テストしたい事項としては下記
get_item
パラメータで指定したIDの商品情報が返される
存在しないIDがパラメータになったら404エラーとなる
想定しないメソッドでリクエストが送られたら405エラーとなる
create_item
まず、get_itemのテストを。
商品情報が返されるか確認します。
JsonResponseの中身(dataの部分)が正しいか確認したいということで探していると dataの部分はresponse.contentとなっていることがわかりました。
>>> from django.http import JsonResponse >>> response = JsonResponse({'foo': 'bar'}) >>> response.content b'{"foo": "bar"}'
しかし、contentはbytes型なのでそのままではテストできません。
調べたら、json.loads()によってcontentをJSON化して内容をテストしている事例がありました。
結果、get_itemによって返される内容を確認するテストは下記のようになりました。
tests.py
import json from django.test import TestCase, Client from django.urls import reverse from .models import Item class GetItem(TestCase): @classmethod def setUpTestData(cls): cls.item = Item.objects.create( name='ゲイシャ', price=500, type=0 ) cls.client = Client() def test_get_item(self): response = self.client.get( path=reverse('items:get', kwargs={'id': 1}) ) # response.contentをJSON化する content = json.loads(response.content) with self.subTest('商品名が返される'): self.assertEqual( 'ゲイシャ', content['name'] ) with self.subTest('値段が返される'): self.assertEqual( 500, content['price'] ) with self.subTest('品種が返される'): self.assertEqual( 'コーヒー', content['type'] )
また、下記でもよかったようです。
content = response.json()
続きましては、get_itemの異常系のテストです。
意図したステータスコードが返されるか確認します。
django.test.Clientによって作成されたdjango.test.Responseオブジェクトはアトリビュートstatus_codeにステータスコードを保持します。
response.status_codeでそのレスポンスのステータスコードがわかります。
def test_not_existing_item(self): response = self.client.get( path=reverse('items:get', kwargs={'id': 2}) ) content = json.loads(response.content) with self.subTest('コンテントは空である'): self.assertFalse( content ) with self.subTest('404エラーで返される'): self.assertEqual( 404, response.status_code ) def test_request_post(self): response = self.client.post( path=reverse('items:get', kwargs={'id': 2}), data={} ) with self.subTest('405エラーで返される'): self.assertEqual( 405, response.status_code )
続いてcreate_itemのテスト。
下記でいけるかな〜と思いましたが、だめでした(^^;
def test_create_item(self): response = self.client.post( path=reverse('items:create'), data={ 'name': 'ハワイコナ', 'price': 800, 'type': 0 }, )
どうやら、content_type="application/json"がないと正しいcontent_typeでリクエストが送られなかった模様。
デフォルトのcontent_typeがMULTIPART_CONTENTなので、そりゃ何も指定しないでうまくいくはずがありませんでした。
結果、create_itemのテストはこうなりました。
def test_create_item(self): response = self.client.post( path=reverse('items:create'), data={ 'name': 'ハワイコナ', 'price': 800, 'type': 0 }, content_type='application/json' ) content = json.loads(response.content) with self.subTest('商品が登録される'): self.assertTrue( Item.objects.filter(name='ハワイコナ', price=800, type=0).exists() ) with self.subTest('登録完了のメッセージが返される'): self.assertEqual( '商品を登録しました', content['message'] ) def test_request_get(self): response = self.client.get( path=reverse('items:create') ) with self.subTest('405エラーで返される'): self.assertEqual( 405, response.status_code )
2つの関数のテストをまとめるとこうなります。
tests.py
import json from django.test import TestCase, Client from django.urls import reverse from .models import Item class GetItem(TestCase): @classmethod def setUpTestData(cls): cls.item = Item.objects.create( name='ゲイシャ', price=500, type=0 ) cls.client = Client() def test_get_item(self): response = self.client.get( path=reverse('items:get', kwargs={'id': 1}) ) content = json.loads(response.content) with self.subTest('商品名が返される'): self.assertEqual( 'ゲイシャ', content['name'] ) with self.subTest('値段が返される'): self.assertEqual( 500, content['price'] ) with self.subTest('品種が返される'): self.assertEqual( 'コーヒー', content['type'] ) def test_not_existing_item(self): response = self.client.get( path=reverse('items:get', kwargs={'id': 2}) ) content = json.loads(response.content) with self.subTest('コンテントは空である'): self.assertFalse( content ) with self.subTest('404エラーで返される'): self.assertEqual( 404, response.status_code ) def test_request_post(self): response = self.client.post( path=reverse('items:get', kwargs={'id': 2}), data={} ) with self.subTest('405エラーで返される'): self.assertEqual( 405, response.status_code ) @classmethod def tearDownClass(cls): pass class CreateItem(TestCase): @classmethod def setUpTestData(cls): cls.client = Client() def test_create_item(self): response = self.client.post( path=reverse('items:create'), data={ 'name': 'ハワイコナ', 'price': 800, 'type': 0 }, content_type='application/json' ) content = json.loads(response.content) with self.subTest('商品が登録される'): self.assertTrue( Item.objects.filter(name='ハワイコナ', price=800, type=0).exists() ) with self.subTest('登録完了のメッセージが返される'): self.assertEqual( '商品を登録しました', content['message'] ) def test_request_get(self): response = self.client.get( path=reverse('items:create') ) with self.subTest('405エラーで返される'): self.assertEqual( 405, response.status_code ) @classmethod def tearDownClass(cls): pass
まあ、DRFでやれよという話ですが、そちらはおいおい出来たらと思います!