0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

djangoテストのassertQuerySetEqualメソッドに関するメモ

Posted at

今回のお題

djangoテストのassertQuerySetEqualメソッドを使うにあたって必要だと感じた知識をメモしておきます。

assertQuerySetEqualメソッドとは

djangoのTestCaseクラスに実装されているテスト用のメソッド。

あるレスポンスに想定通りのquerysetが含まれているかどうかをテストするために用いられる。

assertQuerySetEqualメソッドの基本構文
self.assertQuerySetEqual(response.context["キー名"], "querysetオブジェクト"[, ordered=True])
# querysetオブジェクト同士の順番を無視する場合はordered=falseを指定する。

querysetオブジェクトとは

読んで字の如くqueryset型のオブジェクトのこと。

queryset型とはdjangoに用意されているオブジェクトタイプの一つであり、filter, all, excludeなどのメソッドで取得されたリストにはquerysetというオブジェクトタイプが与えられる(単一のオブジェクトの場合にはqueryset型にはならない)。

オブジェクトタイプの例
# モデルの例
class Book(models.Model):
  title = models.CharField(max_length=20)

  def __str__(self):
    return self.title

# インスタンス
book1 = Book.objects.create(title="坊ちゃん")
book2 = Book.objects.create(title="吾輩は猫である")
book3 = Book.objects.create(title="三四郎")
book_set = Book.objects.all()

# 単体のインスタンスは<モデル名: __str__メソッドの戻り値>
<Book: 坊ちゃん>
# リストの場合は<QuerySet: [<要素1>, <要素2>, ...]>
<QuerySet: [<Book: 坊ちゃん>, <Book: 吾輩は猫である>, <Book: 三四郎>]>

response.contextの中身

次に、assertQuerySetEqulメソッドの第一引数であるresponse.contextの中身について見てみる。

ListVeiwにアクセスした場合
c = Client()
response = c.get(reverse("shop:list"))
print(response.context)
>> [[{'True': True, 'False': False, 'None': None}, {'csrf_token': <SimpleLazyObject: <function csrf.<locals>._get_val at 0x10802c310>>, 'request': <WSGIRequest: GET '/shop/list/'>, 'user': <SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x10802e3d0>>, 'perms': <django.contrib.auth.context_processors.PermWrapper object at 0x10802e0a0>, 'messages': <django.contrib.messages.storage.fallback.FallbackStorage object at 0x108026a30>, 'DEFAULT_MESSAGE_LEVELS': {'DEBUG': 10, 'INFO': 20, 'SUCCESS': 25, 'WARNING': 30, 'ERROR': 40}}, {}, {'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>, 'shop_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>, 'view': <shop.views.ShopListView object at 0x108026d00>}], [{'True': True, 'False': False, 'None': None}, {'csrf_token': <SimpleLazyObject: <function csrf.<locals>._get_val at 0x10802c310>>, 'request': <WSGIRequest: GET '/shop/list/'>, 'user': <SimpleLazyObject: <django.contrib.auth.models.AnonymousUser object at 0x10802e3d0>>, 'perms': <django.contrib.auth.context_processors.PermWrapper object at 0x10802e0a0>, 'messages': <django.contrib.messages.storage.fallback.FallbackStorage object at 0x108026a30>, 'DEFAULT_MESSAGE_LEVELS': {'DEBUG': 10, 'INFO': 20, 'SUCCESS': 25, 'WARNING': 30, 'ERROR': 40}}, {}, {'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>, 'shop_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>, 'view': <shop.views.ShopListView object at 0x108026d00>}]]

色々とリクエストに関する情報が出てくるが、以下に注目する。

'object_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>, 'shop_list': <QuerySet [<Shop: テスト店舗>, <Shop: テスト店舗0>, <Shop: テスト店舗1>]>

ListViewはmodel, queryset, get_querysetメソッドのいずれかでテンプレートに表示するオブジェクトを指定する。

このオブジェクトのリストはobject_list(およびモデル名_list)というキーでcontextの中に格納される(すなわち同一のquerysetが複数のキーに対応することになる。ただし、get_querysetメソッドの処理が複雑な場合にはmodel名_listというキーが作られない場合もある)。

また、DetailViewのような単一のオブジェクトを返すビューではキー名はobject_list, モデル名_listではなくobjectモデル名になる。

DetailViewの場合
print(response.context)
>>
# 中略
'object': <Shop: テスト店舗1>, 'shop': <Shop: テスト店舗1>
# 以下省略

DetaiViewなどであっても、get_context_dataメソッドの中などで別途取得したオブジェクトに関してはリスト形式であればqueryset型になる。

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context["canceled_orders"] = self.object.order_set.dead()
    return context

print(context)
>>
'canceled_orders': <LogicalDeletionQuerySet: []>
# LogicalDeletionMixinを継承しているオブジェクトに関してはLogicalDeletionQuerySet型になる。

まとめ

レスポンスにquerysetが含まれる場合には、assertQuerySetEqualメソッドを用いてテンプレートに渡されたオブジェクトを確認する。

単一のオブジェクトの場合にはassertQuerySetEqualメソッドは使えない(使う必要もない)ので、assertEqualメソッドを用いてオブジェクトが期待通りかを検証する(キーはobjectまたはモデル名)

assertQuerySetEqualパターン
def test_list_view(self):
  response = Client().get(reverse("Book:list"))
  self.assetQuerySetEqual(response.context["object_list"], [<Book: 坊ちゃん>, <Book: 吾輩は猫である>, <Book: 三四郎>], ordered=False)
  # contextのキーはbook_listでもOK
  self.assetQuerySetEqual(response.context["book_list"], [<Book: 坊ちゃん>, <Book: 吾輩は猫である>, <Book: 三四郎>], ordered=False)
  # modelやget_querysetメソッドではなくget_context_dataでセットしたquerysetについてはその時のキーを用いる
  self.assetQuerySetEqual(response.context["canceled_orders"], [])
  # assertQuerySetEqualメソッドは、LogicalDeleteQuerySetに対しても使用可能。
単一オブジェクトパターン
def test_detail_view(self):
    book = Book.objects.first()
    response = Client().get(reverse("Book:detail", kwargs={"pk": book.pk))
    self.assertEqual(response.context["book"], book)
    # 以下でもOK
    self.assertEqual(response.context["object"], book)

QuerySetがリストのオブジェクトタイプの一つであるということ、単一のオブジェクトには使えないこと、そしてcontextのキーの指定方法を押さえておけば基本的には大丈夫かなと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?