41

所以我有各种跨应用程序发送的信号和处理程序。但是,当我执行测试/进入“测试模式”时,我希望禁用这些处理程序。

在测试模式下是否有特定于 Django 的禁用信号/处理程序的方法?我可以想到一个非常简单的方法(在 if TESTING 子句中包含处理程序),但我想知道 Django 中是否有更好的方法?...

4

9 回答 9

118

我在为一组测试用例禁用信号时发现了这个问题,Germano 的回答让我找到了解决方案,但它采用了相反的方法,所以我想我会添加它。

在您的测试课程中:

class MyTest(TestCase):
    def setUp(self):
        # do some setup
        signals.disconnect(listener, sender=FooModel)

我没有添加决策代码来添加信号,而是在测试时禁用了它,这对我来说是一个更好的解决方案(因为测试应该围绕代码编写,而不是围绕测试编写代码)。希望对同一条船上的人有用!

编辑:自从写这篇文章以来,我被介绍了另一种禁用信号进行测试的方法。这需要factory_boy包(v2.4.0+),它对于简化 Django 中的测试非常有用。你真的被宠坏了:

import factory
from django.db.models import signals

class MyTest(TestCase):
    @factory.django.mute_signals(signals.pre_save, signals.post_save)
    def test_something(self):

感谢 ups 的警告:它在工厂内部和创建对象时使信号静音,但在您想要显式 save() 时不会进一步在内部测试 - 信号将在那里取消静音。如果这是一个问题,那么在 setUp 中使用简单的断开连接可能是可行的方法。

于 2014-10-21T15:46:01.280 回答
23

如果您不想使用 FactoryBoy,这是一个完整的示例,其中包含有关如何在测试中禁用特定信号的导入。

from django.db.models import signals
from myapp.models import MyModel

class MyTest(TestCase):

    def test_no_signal(self):
        signals.post_save.disconnect(sender=MyModel, dispatch_uid="my_id")

        ... after this point, the signal is disabled ...

这应该与您的接收器匹配,此示例将匹配此接收器:

@receiver(post_save, sender=MyModel, dispatch_uid="my_id")

我试图在不指定dispatch_uid的情况下禁用信号,但它不起作用。

于 2016-06-30T07:38:38.767 回答
12

不,那里没有。您可以轻松地建立条件连接:

import sys

if not 'test' in sys.argv:
    signal.connect(listener, sender=FooModel)
于 2013-08-30T12:14:17.950 回答
8

除了我使用@krischan 的Factory Boy时,所有答案都对我不起作用。

在我的情况下,我想禁用属于另一个包django_elasticsearch_dslreciever的信号,但我无法找到dispatch_uid.

我不想添加 Factory Boy 包,我设法通过阅读其代码来禁用信号以了解信号是如何静音的,结果非常简单:

from django.db.models import signals

class MyTest(TestCase):
    def test_no_signal(self):
        signals.post_save.receivers = []

我们可以用post_save我们想要禁用的适当信号替换,也可以将其放入setUp所有测试的方法中。

于 2020-10-24T19:36:44.967 回答
4

如果您将接收器连接到AppConfig.ready文档中推荐的信号,请参阅https://docs.djangoproject.com/en/2.2/topics/signals/#connecting-receiver-functionsAppConfig ,您可以使用其他方法为您的测试创建替代方案信号接收器。

于 2019-04-10T12:47:05.077 回答
3

我遇到了类似的问题,无法使用signals.post_save.disconnect(). 找到了这种替代方法,它创建了一个装饰器来覆盖SUSPEND_SIGNALS指定测试和信号的设置。可能对同一条船上的任何人都有用。

首先,创建装饰器:

import functools

from django.conf import settings
from django.dispatch import receiver

def suspendingreceiver(signal, **decorator_kwargs):
    def our_wrapper(func):
        @receiver(signal, **decorator_kwargs)
        @functools.wraps(func)
        def fake_receiver(sender, **kwargs):
            if settings.SUSPEND_SIGNALS:
                return
            return func(sender, **kwargs)
        return fake_receiver
    return our_wrapper

用新的装饰器替换信号上的常用@receiver装饰器:

@suspendingreceiver(post_save, sender=MyModel)
def mymodel_post_save(sender, **kwargs):
    work()

override_settings()在您的 TestCase 上使用 Django :

@override_settings(SUSPEND_SIGNALS=True)
class MyTestCase(TestCase):
    def test_method(self):
        Model.objects.create()  # post_save_receiver won't execute

感谢撰写博客的 Josh Smeaton。

于 2018-08-28T20:05:40.890 回答
3

您可以执行以下操作

from factory.django import mute_signals
from django.db.models import signals
def test_your_code():
    with mute_signals(signals.post_save):
        # ... code that doesn't trigger signals
于 2020-06-13T02:20:01.837 回答
2

最简单的方法是使用夹具。只需将此函数放在您的测试文件中,放在任何将自动运行的类之外。

from django.db.models.signals import pre_save, post_save

@pytest.fixture(autouse=True) # Automatically use in tests.
def mute_signals(request):
    post_save.receivers = []
    pre_save.receivers = []

有关更多详细信息,请参阅此https://www.cameronmaske.com/muting-django-signals-with-a-pytest-fixture/

于 2020-09-14T16:55:21.573 回答
0

工厂库有一个功能,您可以将其用作功能上的固定装置。这是有关如何实现它的示例。

from django.db.models import signals
from factory.django import mute_signals

from app.models import Model

@mute_signals(signals.pre_save, signals.post_save)
def function():
    instance = Model.objects.get(id=1)
    instance.attribute = "modify"
    instance.save()
于 2022-01-25T12:22:48.350 回答