Search code examples
pythondjangomockingsignalsdjango-signals

How do I mock a django signal handler?


I have a signal_handler connected through a decorator, something like this very simple one:

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

What I want to do is to mock it with the mock library http://www.voidspace.org.uk/python/mock/ in a test, to check how many times django calls it. My code at the moment is something like:

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)

The problem here is that the original signal handler is called even if mocked, most likely because the @receiver decorator is storing a copy of the signal handler somewhere, so I'm mocking the wrong code.

So the question: how do I mock my signal handler to make my test work?

Note that if I change my signal handler to:

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)

and I mock _support_function instead, everything works as expected.


Solution

  • 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.