我有通过装饰连接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=True
在mock.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?