Search code examples
djangounit-testingdjango-testing

How do I test Django views when they don't return values


I'm new to Django. I've worked through some tutorials, written some models, views, forms etc. but I don't understand how they can be tested since either nothing is returned, or whatever is returned is so tightly bound it doesn't make sense in terms of the test.

For example here are two view classes:

class ListBlogPostView(ListView):
    model = BlogPost
    template_name = 'app/blogpost_list.html'


class CreateBlogPostView(CreateView):
    model = BlogPost
    template_name = 'app/blogpost_edit.html'
    form_class = BlogPostForm

    def get_success_url(self):
        return reverse('blogpost-list')

    def get_context_data(self, **kwargs):
        context = super(CreateBlogPostView, self).get_context_data(**kwargs)
        context['action'] = reverse('blogpost-new')
        return context

The first, ListBlogPostView, doesn't return anything. How can I ever check that this is working correctly?

The second has a couple of functions but their return values are not things I can test with an assert

How could I ever use a TDD approach to Django?

I'm used to using nunit and MS 'unit' tests in Visual Studio, mocking etc


Solution

  • Actually, Django's generic class-based views are not called generic for no reason.

    You can view the source of the both ListView and CreateView.


    The ListView has a GET method handler:

    class BaseListView(MultipleObjectMixin, View):
        """A base view for displaying a list of objects."""
        def get(self, request, *args, **kwargs):
            self.object_list = self.get_queryset()
            allow_empty = self.get_allow_empty()
    
            if not allow_empty:
               # ...
    
            context = self.get_context_data()
            return self.render_to_response(context)
    

    Which returns a valid Django response and can be tested in tests.py.


    If you look at the CreateView, it inherits from BaseCreateView (just like ListView inherits from BaseListView):

    class BaseCreateView(ModelFormMixin, ProcessFormView):
        """
        Base view for creating an new object instance.
        Using this base class requires subclassing to provide a response mixin.
        """
        def get(self, request, *args, **kwargs):
            # ...
    
        def post(self, request, *args, **kwargs):
            # ...
    

    Which also inherits from ProcessFormView:

    class ProcessFormView(View):
        """Render a form on GET and processes it on POST."""
        def get(self, request, *args, **kwargs):
            """Handle GET requests: instantiate a blank version of the form."""
            return self.render_to_response(self.get_context_data())
    
        def post(self, request, *args, **kwargs):
            """
            Handle POST requests: instantiate a form instance with the passed
            POST variables and then check if it's valid.
            """
            form = self.get_form()
            if form.is_valid():
                return self.form_valid(form)
            else:
                return self.form_invalid(form)
    

    The GET request will result in a valid response. As you see, the POST method handler here returns either self.form_valid(form) or self.form_invalid(form) depending on the form status.

    You can see the source of these two methods in ViewMixin:

    def form_valid(self, form):
        """If the form is valid, redirect to the supplied URL."""
        return HttpResponseRedirect(self.get_success_url())
    
    def form_invalid(self, form):
        """If the form is invalid, render the invalid form."""
        return self.render_to_response(self.get_context_data(form=form))
    

    Both of these methods return a valid testable Django responses.


    In conclusion, both of your ListBlogPostView and CreateBlogPostView can be directly tested in tests.py. You just need to have a more detailed look at the implementation of Django's generic views. The power of open-source!