在 class 实例的方法中修补变量

Patch a variable inside a method of a class instance

我正在尝试了解补丁的工作原理,并且我正在使用 Django 视图的 pytest 进行测试:

views.py

from django.contrib.auth.views import LoginView

class MyLoginView(LoginView):
    pass

test_view.py

from django.test import RequestFactory
from .views import MyLoginView

rf = RequestFactory()

def test_login(rf):
    request = rf.get(reverse('myapp:login'))
    response = MyLoginView.as_view()(request)
    assert response.status_code == 200

失败是因为此视图调用数据库以使用函数 get_current_site():

获取当前站点

Failed: Database access not allowed

如何模拟 get_current_site() 以避免数据库命中?

一个想法是使用带有 pytest-factoryboy 的工厂。

我设法模拟了 LoginView.get_context_data 但我无法更深入:

from django.test import RequestFactory
from .views import MyLoginView

from django.contrib.sites.models import Site
from pytest_factoryboy import register
from unittest.mock import patch

rf = RequestFactory()


class SiteFactory(factory.Factory):
    class Meta:
        model = Site

register(SiteFactory)


def test_login_social(rf, site_factory):
    request = rf.get(reverse('myapp:login'))
    with patch(
        # 'django.contrib.auth.views.LoginView.get_context_data',  # This is wrong
        'django.contrib.auth.views.get_current_site',  # Solution: Patch where it is imported, this works!
        return_value=site_factory(name='example.com'),
    ):
        response = CommunityLoginView.as_view()(request)
    assert response.status_code == 200

编辑

解决方案是在 imported 的范围内修补被调用的方法:

with patch('django.contrib.auth.views.get_current_site')


此处由于context_data<class 'django.contrib.sites.models.Site'>

而发生错误

你会怎么做?

这里有两个选择:

  1. pytest 只允许 database access, if you explicitly mark the test function that we will hit the database. Without that information, pytest will run the test without having constructed a database for tests. I recommend to use pytest-django and the provided decorator pytest.mark.django_db.

  2. 您已将 Site-Framework 添加到您的 INSTALLED_APPS。此应用程序是可选的,但如果您从单个 Django 应用程序提供多个不同的页面,则很有用。曾经有一段时间 Site-Framework 是强制性的,但由于它是可选的,所以我很少将其包含在我的 INSTALLED_APPS 中。也许你应该把它留给。


编辑:模拟

当然,模拟也应该有效,因为 python 中的每个对象都是可模拟的(even small numbers). Keep in mind that you have to patch where the module/function is imported 因为它绑定到本地范围。

要找到正确的位置,您可以搜索 Django source code,看看它是如何使用的以及如何正确修补它,或者尝试放入 PDB。我不确定哪种方式可行,但我为您提供了 2 个选项:

  1. pytest --pdb
  2. python -m pdb pytest。这将立即打开调试器,您必须 continue 一次。 pytest 现在将 运行 直到第一个异常发生并且 PDB 将自动启动。

您现在可以使用 bt(回溯)、u(向上遍历堆栈)、l(显示源代码)和 d(向下遍历堆栈)找到数据库访问的位置。


EDIT2:factoryboy

如果您使用 factoryboy,这取决于 build strategy 它是否尝试访问数据库。默认策略是.create(),写入数据库。

如果您使用 site_factory.build(),它应该可以工作,因为这不会访问您的数据库。