19

I need to test the methods and helper function inside a django Class Based View.

Consider this Class Based View:

class MyClassBasedView(View):

    def dispatch(self, request, *args, **kwargs):
        ....

    def __get_render_dict():
        d = {}
        ...
        return d

    def my_method(self):
        render_dict =  self.__get_render_dict()
        return render_response(self.request, 'template.html', render_dict)

In order to write unit tests for my view, I need to call the methods inside, say __get_render_dict() directly. How can I achieve this?.

I've tried

v = MyClassedBasedView() 
v.dispatch(request,args, kwargs)
v.__method_name()

but this fails with not matching parameters in post/get method, even though I was calling the method direclty without using URL.

Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
totoro
  • 3,257
  • 5
  • 39
  • 61
  • 1
    What do you mean methods inside `__get_render_dict`? – Burhan Khalid Nov 11 '15 at 07:15
  • 3
    and ahem... why are YOU instantiating the CBV directly (and dispatching)? Django does that for you, just insert the view in `urls.py` as `url(r'whateverpattern$',MyClassBasedView.as_view(), name='viewname'),` – Pynchia Nov 11 '15 at 08:05
  • And also, please indent your code properly just to be certain that we all understand the same thing. – Wtower Nov 11 '15 at 08:53
  • Did my answer solve your problem? If yes please accept, if no please specify your question. – Sebastian Wozny Nov 11 '15 at 11:50

3 Answers3

26

To use class based views in your unittests try setup_view from here.

def setup_view(view, request, *args, **kwargs):
    """
    Mimic ``as_view()``, but returns view instance.
    Use this function to get view instances on which you can run unit tests,
    by testing specific methods.
    """

    view.request = request
    view.args = args
    view.kwargs = kwargs
    return view

You still need to feed it a request, you can do this with django.test.RequestFactory:

    factory = RequestFactory()
    request = factory.get('/customer/details')

You can then unittest your methods:

v = setup_view(MyClassedBasedView(), request) 
v.method_name()
Braiam
  • 1
  • 11
  • 47
  • 78
Sebastian Wozny
  • 16,943
  • 7
  • 52
  • 69
  • yes, i did use this for the unit test. however, i do want to call the specific helper function that's not mapped to any URL: say `/customer/details ` calls func1, which callers helper 1. how do i call helper1? – totoro Nov 11 '15 at 13:13
  • `setup_view` returns the view object. You can then call `Myview.helper(*args)`, even if it is not called by the url passed. `setup_view` doesn't call `dispatch`. – Sebastian Wozny Nov 11 '15 at 13:19
  • thanks for the update! can i still call `dispatch` though? because it adds some variables i.e. `self.foo` that would be used by all other methods. – totoro Nov 11 '15 at 13:24
  • If you want to call different methods, you're writing an integration test. If you want to unittest a helper method, just add the variable yourself.: `v.foo=5` – Sebastian Wozny Nov 11 '15 at 13:26
  • do you mean passing setup_view `MyClassBasedView()`, the instantiated version? when it's `MyClassBasedView`, i get error `type object 'MyView' has no attribute '__helper_method' `. but when i do use the instantiated version, i get `'MyView' object has no attribute '__helper_method`. any idea why? thanks! – totoro Nov 11 '15 at 14:08
  • 1
    just use `v.helper_method()` the `__` marks this method as magic. Refer to http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python for an explanation – Sebastian Wozny Nov 11 '15 at 14:10
  • How to test third-party API calls with mocking? – Kishan Mehta Mar 09 '19 at 11:42
16

Update - available in Django 3.0

As stated in Sebastian's answer he got the code snippet from django-downloadview docs. In there they state:

This is an early implementation of https://code.djangoproject.com/ticket/20456

A few years later, this feature is now part of Django, as you can read in the docs, so you would just need to do:

from django.test import RequestFactory, TestCase
from .views import MyClassBasedView

class MyClassBasedViewTest(TestCase):
  def test_my_method(self):
    request = RequestFactory().get('/')
    view = MyClassBasedView()
    view.setup(request)

    view.my_method()

The view.setup() method is precisely what was suggested in the accepted answer, but I think it is better to use the one from Django :)

NBajanca
  • 3,544
  • 3
  • 26
  • 53
  • If I try view.get(), this throws the error `TypeError: get() missing 1 required positional argument: 'request'`. Don't most, if not all view methods, take request as an argument? – Cerin Nov 22 '20 at 19:15
  • @Cerin You can create a request using Django's `RequestFactory`. Import (`from django.test import RequestFactory`) then create your request (e.g. `request = RequestFactory().get('your-url')`) – arcanemachine Sep 01 '22 at 10:27
9

I solved this issue by doing MyClassedBasedView.as_view()(request)

Thom
  • 1,473
  • 2
  • 20
  • 30
  • This is correct. The only caveat is that any variables extracted from URL patterns isn't done automatically, so you need to manually specify these parameters in the call. So if your URL pattern captures "" then you'd call the view like: `MyClassedBasedView.as_view()(request, slug="some-slug")` – Cerin Nov 22 '20 at 19:27