Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing custom Django middleware without using Django itself

I have coded my custom Django middleware in the 1.10 style, similar to this:

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

Note, that this middleware is being threaten as a separate Python module, not belonging to any particular application in my project, but living outside and being installed like any other package, via pip. It does not work itself, but only if installed in Django app.

It works fine, however, I would like to test it. What I've made so far is something like this in 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

I don't know how to call middleware on request variable to modify it. I think it would be much easier if I used function-like middleware style, but what if I'm stuck with what I have and I am supposed ony to write tests, without modifying middleware?

like image 297
Photon Light Avatar asked Mar 23 '17 17:03

Photon Light


People also ask

Can we create custom middleware in Django?

Custom middleware in Django is created either as a function style that takes a get_response callable or a class-based style whose call method is used to process requests and responses. It is created inside a file middleware.py . A middleware is activated by adding it to the MIDDLEWARE list in Django settings.

How do I test my Django site?

The preferred way to write tests in Django is using the unittest module built-in to the Python standard library. This is covered in detail in the Writing and running tests document. You can also use any other Python test framework; Django provides an API and tools for that kind of integration.

Does Django use Pytest or Unittest?

Writing testsDjango's unit tests use a Python standard library module: unittest . This module defines tests using a class-based approach.

How do I run a TestCase in Django?

Open /catalog/tests/test_models.py.TestCase , as shown: from django. test import TestCase # Create your tests here. Often you will add a test class for each model/view/form you want to test, with individual methods for testing specific functionality.


2 Answers

The problem is that you are not calling neither the constructor of MyMiddleware neither invoking the __call__ magic method by invoking the instance of a MyMiddleware object.

There are many ways to test the behaviour that you described, I can think of this one:

First, I slightly modified your example to be self contained:

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'

Next, I created the tests by actually creating the Middleware object and invoking the newly created object as it was a function (so __call__ is run)

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'

Here there are some useful links:

  • Difference between __call__ and __init__ in another SO question: __init__ or __call__?

  • Where to patch from the python docs: https://docs.python.org/3/library/unittest.mock.html#where-to-patch

  • pytest docs: http://docs.pytest.org/en/latest/contents.html

  • ipdb intro, useful for debugging: https://www.safaribooksonline.com/blog/2014/11/18/intro-python-debugger/

like image 94
Enrique Saez Avatar answered Oct 20 '22 00:10

Enrique Saez


Okay, this is a bit late but I had a similar problem and want to provide answer for Googlers.

First, some answers I've come up with suggests to create a HttpRequest instance with setting up a RequestFactory in tests, but this section clearly puts that generating a HttpRequest instance with a RequestFactory does not work like a HTTP server and continues:

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

I already want to test if my injection is in the HttpRequest object, so we have to create one. First, we create a dummy view:

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

Then, we need to hook this up to a URL in urls.py.

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

If you already use pytest-django, then you can easily get a HttpClient instance with client fixture, call this URL and get HttpRequest from response as below:

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")

This has some drawbacks though:

  • It spawns a real development server process, so it makes testing kind of slow.
  • Creating a dummy view might not be a viable solution for a real Django project because you might need to wrap your URL with too much boilerplate such as if DEBUG. I find this useful only for 3rd party modules.
like image 37
Eray Erdin Avatar answered Oct 19 '22 22:10

Eray Erdin