variable template tag registered one not must library ifequal examples python django mocking signals django-signals

python - template - ifequal django



¿Cómo me burlo de un manejador de señal django? (6)

Tengo un signal_handler conectado a través de un decorador, algo así como este muy simple:

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

Lo que quiero hacer es burlarlo con la biblioteca simulada http://www.voidspace.org.uk/python/mock/ en una prueba, para comprobar cuántas veces lo llama django. Mi código en este momento es algo así como:

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)

El problema aquí es que se llama al manejador de señal original incluso si se burla, muy probablemente porque el decorador @receiver está almacenando una copia del manejador de señal en alguna parte, así que me estoy burlando del código equivocado.

Entonces la pregunta: ¿cómo me burlo de mi manejador de señal para hacer que mi prueba funcione?

Tenga en cuenta que si cambio mi manejador de señal a:

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)

y me burlo de _support_function en _support_function lugar, todo funciona como se esperaba.


En django 1.9 puedes simular todos los receptores con algo como esto

# 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)

Esto reemplaza a todos los receptores con simulacros, por ejemplo, los que ha registrado, las aplicaciones conectables que se han registrado y las que django mismo ha registrado. No se sorprenda si usa esto en post_save y las cosas comienzan a romperse.

Es posible que desee inspeccionar el receptor para determinar si realmente quiere burlarse de él.


Entonces, terminé con una especie de solución: burlarse de un manejador de señal simplemente significa conectar el simulador a la señal, así que esto es exactamente lo que hice:

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

Tenga en cuenta que autospec=True en mock.patch es necesario para que post_save.connect funcione correctamente en MagicMock , de lo contrario, django generará algunas excepciones y la conexión fallará.


Hay una forma de burlarse de las señales de django con una clase pequeña.

Debes tener en cuenta que esto solo simularía la función como manejador de señal django y no la función original; por ejemplo, si un m2mchange implica una llamada a una función que llama directamente a su manejador, no se incrementará el número de cuenta. Necesitarías un simulacro separado para realizar un seguimiento de esas llamadas.

Aquí está la clase en cuestión:

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

Ejemplo de uso

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)


Posiblemente una mejor idea es burlarse de la funcionalidad dentro del manejador de señal en lugar del manejador. Usando el código de 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

Luego do_stuff :

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


Puede simular una señal django burlándose de la clase django.db.models.signals.py en django.db.models.signals.py siguiente manera:

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

Eso debería hacer el truco. Tenga en cuenta que esto simulará TODAS las señales sin importar qué objeto esté usando.

Si por casualidad usas la biblioteca de mocker , puedes hacerlo de la siguiente manera:

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

Son más líneas pero funciona bastante bien también :)