Reputation: 57521
In our Django project, we have a receiver function for post_save
signals sent by the User
model:
@receiver(post_save, sender=User)
def update_intercom_attributes(sender, instance, **kwargs):
# if the user is not yet in the app, do not attempt to setup/update their Intercom profile.
if instance.using_app:
intercom.update_intercom_attributes(instance)
This receiver calls an external API, and we'd like to disable it when generating test fixtures with factory_boy
. As far as I can tell from https://factoryboy.readthedocs.io/en/latest/orms.html#disabling-signals, however, all one can do is mute all post_save
signals, not a specific receiver.
At the moment, the way we are going about this is by defining an IntercomMixin
which every test case inherits from (in the first position in the inheritance chain):
from unittest.mock import patch
class IntercomMixin:
@classmethod
def setUpClass(cls):
cls.patcher = patch('lucy_web.lib.intercom.update_intercom_attributes')
cls.intercomMock = cls.patcher.start()
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.patcher.stop()
However, it is cumbersome and repetitive to add this mixin to every test case, and ideally, we'd like to build this patching functionality into the test factories themselves.
Is there any way to do this in Factory Boy? (I had a look at the source code (https://github.com/FactoryBoy/factory_boy/blob/2d735767b7f3e1f9adfc3f14c28eeef7acbf6e5a/factory/django.py#L256) and it seems like the __enter__
method is setting signal.receivers = []
; perhaps this could be modified so that it accepts a receiver
function and pops it out of the the signal.receivers
list)?
Upvotes: 3
Views: 1345
Reputation: 1018
I had a similar use case and looks like there is no direct support to mute the specific receivers of a signal.
I'd to implement a context manager to disconnect a receiver and restore it after the block of code is executed.
from contextlib import contextmanager
from django.dispatch import Signal
from typing import Any, Type
@contextmanager
def muted_receiver(
signal: Type[Signal],
receiver: callable,
sender: Any,
dispatch_uid=None,
weak: bool = True,
failsafe=False,
):
disconnected = signal.disconnect(
receiver=receiver,
sender=sender,
dispatch_uid=dispatch_uid,
)
if not disconnected and not failsafe:
raise ValueError(f"Couldn't disconnect {signal}.")
yield
# restore the signal
signal.connect(
receiver=receiver,
sender=sender,
weak=weak,
dispatch_uid=dispatch_uid,
)
assert receiver in signal._live_receivers(sender=sender)
Upvotes: 0
Reputation: 9441
In case you want to mute all signals of a type, you can configure that on your factory directly. For example:
from django.db.models.signals import post_save
@factory.django.mute_signals(post_save)
class UserFactory(DjangoModelFactory):
...
Upvotes: 1
Reputation: 438
For anyone looking for just this thing and finding themselves on this question you can find the solution here: https://stackoverflow.com/a/26490827/1108593
Basically... call @factory.django.mute_signals(post_save)
on the test method itself; or in my case the setUpTestData method.
Test:
# test_models.py
from django.test import TestCase
from django.db.models.signals import post_save
from .factories import ProfileFactory
import factory
class ProfileTest(TestCase):
@classmethod
@factory.django.mute_signals(post_save)
def setUpTestData(cls):
ProfileFactory(id=1) # This won't trigger user creation.
...
Profile Factory:
#factories.py
import factory
from factory.django import DjangoModelFactory
from profiles.models import Profile
from authentication.tests.factories import UserFactory
class ProfileFactory(DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
This allows your factories to keep working as expected and the tests to manipulate them as needed to test what they need.
Upvotes: 1