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

Testing custom Django middleware without using Django itself

我在 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 变量上调用中间件来修改它。我认为如果我使用类似函数的中间件样式会容易得多,但是如果我坚持使用现有的东西并且我应该只编写测试而不修改中间件怎么办?

问题是您既没有调用 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'

这里有一些有用的链接:

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

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

It does not support middleware. Session and authentication attributes must be supplied by the test itself if required for the view to function properly.

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

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

然后,我们需要将其连接到 urls.py 中的 URL。

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

如果您已经使用了 pytest-django,那么您可以轻松地使用 client fixture 获取 HttpClient 实例,调用此 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。我发现这仅对第 3 方模块有用。