在不使用 Django 本身的情况下测试自定义 Django 中间件

Posted

技术标签:

【中文标题】在不使用 Django 本身的情况下测试自定义 Django 中间件【英文标题】:Testing custom Django middleware without using Django itself 【发布时间】:2017-08-16 10:48:28 【问题描述】:

我在1.10 style 中编写了我的自定义 Django 中间件,类似于:

class MyMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response
        # some initialization stuff here

    def __call__(self, request):
        # Code executed before view functions are called. 
        # Purpose of this middeware is to add new attribute to request

        # In brief:
        request.new_attribute = some_function_returning_some_object()
        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

请注意,这个中间件作为一个单独的 Python 模块受到威胁,它不属于我项目中的任何特定应用程序,而是存在于外部并像任何其他包一样通过 pip 安装。它本身不起作用,但只有安装在 Django 应用程序中。

它工作正常,但是,我想对其进行测试。到目前为止,我在my_tests.py 中所做的是这样的:

from my_middleware_module import MyMiddleware
# some @patches
def test_mymiddleware():
    request = Mock()
    assert hasattr(request, 'new_attribute') is False # passes obviously
    # CALL MIDDLEWARE ON REQUEST HERE
    assert hasattr(request, 'new_attribute') is True # I want it to pass

我不知道如何在request 变量上调用中间件来修改它。我认为如果我使用类似函数的中间件样式会容易得多,但是如果我坚持我所拥有的并且我应该只编写测试而不修改中间件怎么办?

【问题讨论】:

【参考方案1】:

问题是您既没有调用MyMiddleware 的构造函数,也没有通过调用MyMiddleware 对象的实例来调用__call__ 魔术方法。

有很多方法可以测试你描述的行为,我可以想到这个:

首先,我将您的示例稍微修改为自包含:

class MyMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        request.new_attribute = some_function_returning_some_object()
        response = self.get_response(request)
        return response

def some_function_returning_some_object():
    return 'whatever'

接下来,我通过实际创建中间件对象并调用新创建的对象来创建测试,因为它是一个函数(因此运行__call__

from mock import patch, Mock
from middle import MyMiddleware
import unittest


class TestMiddleware(unittest.TestCase):

    @patch('middle.MyMiddleware')
    def test_init(self, my_middleware_mock):
        my_middleware = MyMiddleware('response')
        assert(my_middleware.get_response) == 'response'

    def test_mymiddleware(self):
        request = Mock()
        my_middleware = MyMiddleware(Mock())
        # CALL MIDDLEWARE ON REQUEST HERE
        my_middleware(request)
        assert request.new_attribute == 'whatever'

这里有一些有用的链接:

另一个 SO 问题中 __call__ 和 __init__ 之间的区别:__init__ or __call__?

从 python 文档中修补的位置:https://docs.python.org/3/library/unittest.mock.html#where-to-patch

pytest 文档:http://docs.pytest.org/en/latest/contents.html

ipdb 介绍,对调试有用:https://www.safaribooksonline.com/blog/2014/11/18/intro-python-debugger/

【讨论】:

【参考方案2】:

好的,这有点晚了,但我遇到了类似的问题,想为 Google 员工提供答案。

首先,我提出的一些答案建议创建一个HttpRequest 实例并在测试中设置RequestFactory,但this section 明确表示生成带有RequestFactoryHttpRequest 实例确实如此不像 HTTP 服务器那样工作并继续:

它不支持中间件。如果视图正常运行需要,会话和身份验证属性必须由测试本身提供。

我已经想测试我的注入 是否HttpRequest 对象中,所以我们必须创建一个。首先,我们创建一个虚拟视图:

def dummy_view(request, *args, **kwargs):
    return HttpResponse(b"dummy")

然后,我们需要将其与urls.py 中的 URL 挂钩。

path("/dummy", dummy_view, name="dummy")

如果你已经使用pytest-django,那么你可以很容易地得到一个HttpClient 实例和client 夹具,调用这个URL 并从响应中得到HttpRequest,如下所示:

def test_request_injection_exists(client):
    response = client.get(reverse("dummy"))
    request = response.wsgi_request  # this has `HttpRequest` instance

    # assuming I injected an attribute called `foo`
    assert hasattr(request, "foo")

这有一些缺点:

它会产生一个真正的开发服务器进程,因此它会使测试有点变慢。 对于真正的 Django 项目来说,创建一个虚拟视图可能不是一个可行的解决方案,因为您可能需要使用太多样板文件来包装您的 URL,例如 if DEBUG。我发现这仅对 3rd 方模块有用。

【讨论】:

以上是关于在不使用 Django 本身的情况下测试自定义 Django 中间件的主要内容,如果未能解决你的问题,请参考以下文章

如何在不安装完整框架的情况下测试自定义 Laravel 控制台命令?

如何在不定义内容类型或模型的情况下使用 Django 权限?

我可以在不创建表单的情况下创建 Django 中不需要的管理字段吗?

如何使用自定义 django 404 错误视图重定向?

是否可以在不使用 Oracle MCS 中的连接器的情况下创建自定义 API?

如何在不导入自定义小部件类包的情况下使用自定义小部件和 uic.loadUi?