How to test signals when using factory_boy with muted signals

拜拜、爱过 提交于 2021-02-18 15:18:48

问题


I am using factory_boy package and DjangoModelFactory to generate a factory model with muted signals

@factory.django.mute_signals(signals.post_save) class SomeModelTargetFactory(DjangoModelFactory): name = factory.Sequence(lambda x: "Name #{}".format(x)) ...

I have a post_save signal connected to the model: def send_notification(sender, instance, created, **kwargs): if created: send_email(...) post_save.connect(send_notification, SomeModel)

How can I test the signals works when I create an instance of the model using the factory class?


回答1:


Some solutions for the direct question. Followed by a caution.

A) Instead of turning off the signals, mock the side effects

@mock.patch('send_email')
def test_mocking_signal_side_effects(self, mocked_send_email):
    my_obj = SomeModelTargetFactory()

    # mocked version of send_email was called
    self.assertEqual(mocked_send_email.call_count, 1)

    my_obj.foo = 'bar'
    my_obj.save()

    # didn't call send_email again
    self.assertEqual(mocked_send_email.call_count, 1)

Note: mock was separate package before joining standard lib in 3.3

B) Use as context manager so you can selectively disable in your tests

This would leave the signals on by default, but you can selectively disable:

def test_without_signals(self):
    with factory.django.mute_signals(signals.post_save):
        my_obj = SomeModelTargetFactory()

        # ... perform actions w/o signals and assert  ...

C) Mute signals and an extended version of the base factory

class SomeModelTargetFactory(DjangoModelFactory):
    name = factory.Sequence(lambda x: "Name #{}".format(x))
    # ...


@factory.django.mute_signals(signals.post_save)
class SomeModelTargetFactoryNoSignals(SomeModelTargetFactory):
    pass

I've never tried this, but it seems like it should work. Additionally, if you just need the objects for a quick unit test where persistence isn't required, maybe FactoryBoy's BUILD strategy is a viable option.

Caution: Muting signals, especially like post_save can hide nasty bugs

There are easily findable references about how using signals in your own code can create a false sense of decoupling (post_save for example, essentially is the same as overriding and extending the save method. I'll let you research that to see if it applies to your use case.

Would definitely think twice about making it the default.

A safer approach is to "mute"/mock the receiver/side effect, not the sender.

The default Django model signals are used frequently by third party packages. Muting those can hide hard to track down bugs due to intra-package interaction.

Defining and calling (and then muting if needed) your own signals is better, but often is just re-inventing a method call. Sentry is a good example of signals being used well in a large codebase.

Solution A is by far the most explicit and safe. Solution B and C, without the addition of your own signal requires care and attention.

I wont say there are no use cases for muting post_save entirely. It should be an exception and an alert to maybe double check the need in the first place.



来源:https://stackoverflow.com/questions/45320964/how-to-test-signals-when-using-factory-boy-with-muted-signals

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!