Djangoのシグナルハンドラをテストする

Djangoのsignalのコールバック(ハンドラ)をテストする

signalのセットアップ

Djangoで、シグナルハンドラがコールされているかテストしたい。

シグナルの定義については、前回の記事で触れている:

DjangoでModel.delete()のオーバーライドはしなくてもいい
DjangoのModel.delete()のオーバーライド時の注意点 更新 2023/3: connectはsignals.pyを使っている場合、不要だったので記述を更新。 やりたいこと Djangoにて、DBからのアイテムの削除後にやりた...

myapp/signals.pyにシグナルハンドラを定義して、@receiverでシグナルに紐づける。

このようにして紐づけたシグナルハンドラが実行されていることなどをテストしたい。 また、テスト時にシグナルハンドラが意図せずに実行されることを防ぐ必要もあるだろう。

テスト

Djangoのセットアップにより既にシグナルハンドラが接続されている点に注意すればシンプルに見える。

以下のような実装ができる:

from django.test import TestCase

from myapp.models import MyModel
from myapp.signals import signal_handler

from unittest.mock import MagicMock, patch

class MyModelSignalTestCase(TestCase):
    def setUp(self) -> None:
        # ここでシグナルハンドラを切断しておく方法もある
        # DBのセットアップ(ここでは10行生成と仮定)
        return super().setUp()

    # シグナルハンドラでSomeClassを使うのでパッチする
    @patch("myapp.signals.SomeClass")
    def test_my_handler_is_called(self, mock: MagicMock):
        from django.db.models.signals import post_delete

        # シグナルハンドラが接続されていることを確認する
        # (Djangoのセットアップで接続されている)
        self.assertTrue(post_delete.has_listeners(MyModel))
        self.assertEqual(MyModel.objects.count(), 10)

        # ハンドラを切断して、ハンドラが呼び出されないことを確認する
        post_delete.disconnect(
            receiver=signal_handler,
            sender=MyModel,
            dispatch_uid="my_handler_uid",
        )
        self.assertFalse(post_delete.has_listeners(MyModel))
        MyModel.objects.filter(name="Alice").delete()
        self.assertEqual(MyModel.objects.count(), 9)
        # signalハンドラはsignalを受け取らない
        mock.assert_not_called()

        # ハンドラを接続して、ハンドラが呼び出されることを確認する
        post_delete.connect(
            signal_handler,
            sender=MyModel,
            dispatch_uid="my_handler_uid",
        )
        self.assertTrue(post_delete.has_listeners(MyModel))
        MyModel.objects.filter(name="Bob").delete()
        self.assertEqual(MyModel.objects.count(), 8)

        # signalハンドラは接続したので呼ばれる
        mock.assert_called_once()

シグナルをインポートすれば、has_listeners()でそのシグナルに接続されているハンドラをチェックできる。

おわり

あまりないと思うが、テスト中の意図しないシグナルハンドラの呼び出しには注意しておきたい。

こちらのDjangoドキュメントのほうが検索がしっかり機能しており、実用的だった:

Django documentation — Django 5.0.4 documentation

以上です。

タイトルとURLをコピーしました