如何嘲笑Django的信号处理程序?(How do I mock a django signal h

2019-07-03 10:13发布

我有通过装饰连接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代替,一切正常。

Answer 1:

所以,我结束了那种-的解决方案:嘲笑的信号处理只是手段,模拟本身连接到信号,所以这恰恰是我所做的:

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=Truemock.patch需要为了使post_save.connect正确地在工作MagicMock ,否则Django将提高一些例外,连接将失败。



Answer 2:

可能是一个更好的想法是模拟出的信号处理程序,而不是处理器本身内置的功能。 使用OP的代码:

@receiver(post_save, sender=User, dispatch_uid='myfile.signal_handler_post_save_user')
def signal_handler_post_save_user(sender, *args, **kwargs):
  do_stuff()  # <-- mock this

def do_stuff():
   ... do stuff in here

然后模拟do_stuff

with mock.patch('myapp.myfile.do_stuff') as mocked_handler:
    self.assert_equal(mocked_handler.call_count, 1)


Answer 3:

看看mock_django。 它有信号支持

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



Answer 4:

有一种方法来嘲笑Django的信号与小班。

你应该记住,这只会嘲笑功能Django的信号处理程序,而不是原来的功能; 例如,如果一个m2mchange trigers到直接调用您的处理程序调用一个函数,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)


Answer 5:

您可以通过在嘲讽ModelSignal类嘲笑Django的信号django.db.models.signals.py是这样的:

@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()

它更行,但它工作得很好太:)



Answer 6:

在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 ,事情开始破裂。

您可能要检查的接收器,以确定是否真的想嘲笑它。



Answer 7:

至于你提到的mock.patch('myapp.myfile._support_function')是正确的,但mock.patch('myapp.myfile.signal_handler_post_save_user')是错误的。

我想原因是:

当初始化你测试,一些文件的导入信号的实现Python文件,然后@receive装饰创建一个新的信号连接。

在测试中, mock.patch('myapp.myfile._support_function')将产生另一信号连接,所以如果嘲笑原始信号处理程序甚至称。

尝试断开前的信号连接mock.patch('myapp.myfile._support_function')

post_save.disconnect(signal_handler_post_save_user)
with mock.patch("review.signals. signal_handler_post_save_user", autospec=True) as handler:
    #do stuff


文章来源: How do I mock a django signal handler?