51

次の非常に単純なもののような、デコレータを介して接続された signal_handler があります。

@receiver(post_save, sender=User, 
          dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
   # do stuff

私がやりたいことは、テストでモック ライブラリhttp://www.voidspace.org.uk/python/mock/を使用してモックし、django が呼び出す回数を確認することです。現時点での私のコードは次のようなものです:

def test_cache():
    with mock.patch('myapp.myfile.signal_handler_post_save_user') as mocked_handler:
        # do stuff that will call the post_save of User
    self.assert_equal(mocked_handler.call_count, 1)

ここでの問題は、元のシグナル ハンドラーがモックされていても呼び出されることです。これは、おそらく@receiverデコレータがシグナル ハンドラーのコピーをどこかに保存しているためであり、間違ったコードをモックしています。

質問: シグナル ハンドラーをモックして、テストを機能させるにはどうすればよいですか?

シグナルハンドラを次のように変更すると、次のようになることに注意してください。

def _support_function(*args, **kwargs):
    # do stuff

@receiver(post_save, sender=User, 
          dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
   _support_function(*args, **kwargs)

_support_function代わりにモックを作成すると、すべてが期待どおりに機能します。

4

7 に答える 7

18

だから、私は一種の解決策に行き着きました:シグナルハンドラーをモックすることは、単にモック自体をシグナルに接続することを意味するので、これはまさに私がしたことです:

def test_cache():
    with mock.patch('myapp.myfile.signal_handler_post_save_user', autospec=True) as mocked_handler:
        post_save.connect(mocked_handler, sender=User, dispatch_uid='test_cache_mocked_handler')
        # do stuff that will call the post_save of User
    self.assertEquals(mocked_handler.call_count, 1)  # standard django
    # self.assert_equal(mocked_handler.call_count, 1)  # when using django-nose

を正しく機能させるには、 autospec=Trueinが必要であることに注意してください。そうしないと、djangoでいくつかの例外が発生し、接続が失敗します。mock.patchpost_save.connectMagicMock

于 2012-10-29T09:47:37.533 に答える
4

django.db.models.signals.py次のように ModelSignal クラスをモックすることで、django シグナルをモックできます。

@patch("django.db.models.signals.ModelSignal.send")
def test_overwhelming(self, mocker_signal):
    obj = Object()

これでうまくいくはずです。これは、使用しているオブジェクトに関係なく、すべてのシグナルをモックすることに注意してください。

万が一、mocker代わりにライブラリを使用する場合は、次のように実行できます。

from mocker import Mocker, ARGS, KWARGS

def test_overwhelming(self):
    mocker = Mocker()
    # mock the post save signal
    msave = mocker.replace("django.db.models.signals")
    msave.post_save.send(KWARGS)
    mocker.count(0, None)

    with mocker:
        obj = Object()

それはより多くの行ですが、それもかなりうまく機能します:)

于 2015-09-28T20:38:32.810 に答える
3

django 1.9では、このようなものですべてのレシーバーをモックできます

# replace actual receivers with mocks
mocked_receivers = []
for i, receiver in enumerate(your_signal.receivers):
    mock_receiver = Mock()
    your_signal.receivers[i] = (receiver[0], mock_receiver)
    mocked_receivers.append(mock_receiver)

...  # whatever your test does

# ensure that mocked receivers have been called as expected
for mocked_receiver in mocked_receivers:
    assert mocked_receiver.call_count == 1
    mocked_receiver.assert_called_with(*your_args, sender="your_sender", signal=your_signal, **your_kwargs)

これにより、すべてのレシーバーがモックに置き換えられます。たとえば、登録したもの、プラグイン可能なアプリが登録したもの、および django 自体が登録したものです。post_saveこれを使用して物事が壊れ始めても驚かないでください。

レシーバーを調べて、実際にモックするかどうかを判断することができます。

于 2016-05-26T06:39:11.187 に答える
3

mock_django を見てください。シグナルをサポートしています

https://github.com/dcramer/mock-django/blob/master/tests/mock_django/signals/tests.py

于 2012-10-29T05:41:07.987 に答える
2

小さなクラスでdjangoシグナルをモックする方法があります。

これは、元の関数ではなく、django シグナルハンドラーとして関数をモックするだけであることに注意してください。たとえば、m2mchange がハンドラーを直接呼び出す関数の呼び出しをトリガーする場合、mock.call_count はインクリメントされません。これらの呼び出しを追跡するには、別のモックが必要になります。

問題のクラスは次のとおりです。

class LocalDjangoSignalsMock():
    def __init__(self, to_mock):
        """ 
        Replaces registered django signals with MagicMocks

        :param to_mock: list of signal handlers to mock
        """
        self.mocks = {handler:MagicMock() for handler in to_mock}
        self.reverse_mocks = {magicmock:mocked
                              for mocked,magicmock in self.mocks.items()}
        django_signals = [signals.post_save, signals.m2m_changed]
        self.registered_receivers = [signal.receivers
                                     for signal in django_signals]

    def _apply_mocks(self):
        for receivers in self.registered_receivers:
            for receiver_index in xrange(len(receivers)):
                handler = receivers[receiver_index]
                handler_function = handler[1]()
                if handler_function in self.mocks:
                    receivers[receiver_index] = (
                        handler[0], self.mocks[handler_function])

    def _reverse_mocks(self):
        for receivers in self.registered_receivers:
            for receiver_index in xrange(len(receivers)):
                handler = receivers[receiver_index]
                handler_function = handler[1]
                if not isinstance(handler_function, MagicMock):
                    continue
                receivers[receiver_index] = (
                    handler[0], weakref.ref(self.reverse_mocks[handler_function]))

    def __enter__(self):
        self._apply_mocks()
        return self.mocks

    def __exit__(self, *args):
        self._reverse_mocks()

使用例

to_mock = [my_handler]
with LocalDjangoSignalsMock(to_mock) as mocks:
    my_trigger()
    for mocked in to_mock:
        assert(mocks[mocked].call_count)
        # 'function {0} was called {1}'.format(
        #      mocked, mocked.call_count)
于 2013-12-16T09:22:12.320 に答える