19π
So, I ended up with a kind-of solution: mocking a signal handler simply means to connect the mock itself to the signal, so this exactly is what I did:
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
Notice that autospec=True
in mock.patch
is required in order to make post_save.connect
to correctly work on a MagicMock
, otherwise django will raise some exceptions and the connection will fail.
27π
Possibly a better idea is to mock out the functionality inside the signal handler rather than the handler itself. Using the OPβs code:
@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
Then mock do_stuff
:
with mock.patch('myapp.myfile.do_stuff') as mocked_handler:
self.assert_equal(mocked_handler.call_count, 1)
- [Django]-Django β why is the request.POST object immutable?
- [Django]-Unable to import path from django.urls
- [Django]-RuntimeError: Model class django.contrib.sites.models.Site doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS
4π
You can mock a django signal by mocking the ModelSignal class at django.db.models.signals.py
like this:
@patch("django.db.models.signals.ModelSignal.send")
def test_overwhelming(self, mocker_signal):
obj = Object()
That should do the trick. Note that this will mock ALL signals no matter which object you are using.
If by any chance you use the mocker
library instead, it can be done like this:
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()
Itβs more lines but it works pretty well too π
- [Django]-Tell if a Django Field is required from template
- [Django]-How to change empty_label for modelForm choice field?
- [Django]-Redirect / return to same (previous) page in Django?
3π
take a look at mock_django . It has support for signals
https://github.com/dcramer/mock-django/blob/master/tests/mock_django/signals/tests.py
- [Django]-How can I filter a date of a DateTimeField in Django?
- [Django]-Can't connect to local MySQL server through socket '/tmp/mysql.sock
- [Django]-How to send a correct authorization header for basic authentication
3π
In django 1.9 you can mock all receivers with something like this
# 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)
This replaces all receivers with mocks, eg ones youβve registered, ones pluggable apps have registered and ones that django itself has registered. Donβt be suprised if you use this on post_save
and things start breaking.
You may want to inspect the receiver to determine if you actually want to mock it.
- [Django]-Django is_staff permission decorator
- [Django]-Timestamp fields in django
- [Django]-How to hide some fields in django-admin?
2π
There is a way to mock django signals with a small class.
You should keep in mind that this would only mock the function as a django signal handler and not the original function; for example, if a m2mchange trigers a call to a function that calls your handler directly, mock.call_count would not be incremented. You would need a separate mock to keep track of those calls.
Here is the class in question:
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()
Example usage
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)
- [Django]-How can I get all the request headers in Django?
- [Django]-Custom validation in Django admin
- [Django]-Find Monday's date with Python
1π
As you mentioned,
mock.patch('myapp.myfile._support_function')
is correct but mock.patch('myapp.myfile.signal_handler_post_save_user')
is wrong.
I think the reason is:
When init you test, some file import the signalβs realization python file, then @receive
decorator create a new signal connection.
In the test, mock.patch('myapp.myfile._support_function')
will create another signal connection, so the original signal handler is called even if mocked.
Try to disconnect the signal connection before mock.patch('myapp.myfile._support_function')
, like
post_save.disconnect(signal_handler_post_save_user)
with mock.patch("review.signals. signal_handler_post_save_user", autospec=True) as handler:
#do stuff
- [Django]-Folder Structure for Python Django-REST-framework and Angularjs
- [Django]-How do I use a dictionary to update fields in Django models?
- [Django]-Custom Filter in Django Admin on Django 1.3 or below