2

I am trying to write a test for an UpdateView I created.

My view looks like this:

class MutantUpdateView(UpdateView):
    context_object_name = "mutant"
    fields = ["mutation_level"]
    model = Mutant
    pk_url_kwarg = "mutant_id"
    template_name_suffix = "_update_form"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["admin"] = get_object_or_404(
            Person, id=self.kwargs["admin_id"]
        )
        context["backstory"] = get_object_or_404(
            Backstory, id=self.kwargs["backstory_id"]
        )
        context["evidences"] = self.object.evidences.all()
        return context

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        mutant = self.object
        mutation_levels = (
            mutant.expectation.mutation_levels.all()
        )
        form.fields["mutation_level"].queryset = mutation_levels
        return form

    def form_valid(self, form):
        form.instance.updated_by = = get_object_or_404(
            Person, id=self.request.user.person_id
        )
        return super().form_valid(form)

    def get_success_url(self, **kwargs):
        return reverse_lazy(
            "mutants:mutant-update",
            kwargs={
                "admin_id": self.kwargs["admin_id"],
                "backstory_id": self.kwargs["backstory_id"],
                "mutant_id": self.kwargs["mutant_id"],
            },
        )

My test looks like this (per the documentation)

def test_form_valid_posts_appropriately(self):
    new_level = MutantLevelFactory(short_description="Next Mutant Level")
    self.mutant.expectation.mutation_levels.add(new_level)
    data = {
        "created_by": self.admin_person.id,
        "updated_by": self.admin_person.id,
        "backstory": self.mutant.backstory.id,
        "expectation_level": new_level,
        "expectation": self.mutant.expectation.id,
    }
    kwargs = {
        "admin_id": self.admin_person.id,
        "backstory_id": self.mutant.backstory.id,
        "mutant_id": self.mutant.id,
    }
    request = self.factory.get(
        reverse("mutants:mutant-update", kwargs=kwargs)
    )
    request.user = self.admin_user  # simulate admin user logged in
    self.view.setup(request)
    context = self.view.get_context_data()
    self.assertIn('backstory', context)
    self.assertFalse(context["form"].errors)
    self.assertEqual(response.status_code, 302)

It's giving me this error:

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
>       mutant = self.object
E       AttributeError: 'MutantUpdateView' object has no attribute 'object'

The model looks like this:

class Mutant(models.Model):
    id = models.BigAutoField(primary_key=True)
    backstory = models.ForeignKey(
        Backstory, on_delete=models.PROTECT, related_name="%(class)ss"
    )
    expectation = models.ForeignKey(
        Expectation, on_delete=models.PROTECT, related_name="%(class)ss"
    )
    mutation_level = models.ForeignKey(
        MutationLevel,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        related_name="%(class)ss",
    )

What is the best way to test a basic Django UpdateView? Is this the best way or should I be using the Client() and making an integration type test? Ultimately I'd like to both unit test and integration test this view - if that's appropriate.

I've tried changing the code in various ways such as:

response = MutantUpdateView.as_view()(request, **kwargs)

but that didn't work either.

UpDawg
  • 51
  • 6
  • Your test handles the view the wrong way. I would really advise to use Django's test client: https://docs.djangoproject.com/en/4.1/topics/testing/tools/#the-test-client since this will instantiate and process the request correctly. – Willem Van Onsem Nov 03 '22 at 16:58
  • @WillemVanOnsem how does it handle it the wrong way? I'm curious about how so, because I literally copy/pasted and tweaked what was in the documentation. – UpDawg Nov 03 '22 at 18:12
  • the `.get` sets the object: https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/UpdateView/#get – Willem Van Onsem Nov 03 '22 at 18:25
  • I see, because the test says "posts_appropriately", but that is misleading; I'm just trying to get it to work at the moment. I'll change it to `.post` once I can get the `.get` working a little better so I understand what's happening. As it is, the `.get` isn't working when it's setup like the documentation, so I am looking for help. – UpDawg Nov 03 '22 at 18:48
  • both `.post` and `.get` will set the object before ever reaching the context. – Willem Van Onsem Nov 03 '22 at 18:50

1 Answers1

1

The problem is not the view, but testing the view. Your test aims to "mimic" the code flow of the view, but the object is set in the .get(…) method [classy-Django]. You likely can implement that too, but if you later change the view, or you add a mixin, etc. That will result in fixing all tests.

One usually uses the django test client [Django-doc] for this: a tool that will fake a request, and pass that through the view:

from django.test import TestCase


class MyTestCase(TestCase):
    def test_form_valid_posts_appropriately(self):
        self.client.force_login(self.admin_user)
        new_level = MutantLevelFactory(short_description='Next Mutant Level')
        self.mutant.expectation.mutation_levels.add(new_level)
        data = {
            'created_by': self.admin_person.id,
            'updated_by': self.admin_person.id,
            'backstory': self.mutant.backstory.id,
            'expectation_level': new_level,
            'expectation': self.mutant.expectation.id,
        }
        kwargs = {
            'admin_id': self.admin_person.id,
            'backstory_id': self.mutant.backstory.id,
            'mutant_id': self.mutant.id,
        }
        response = self.client.post(
            reverse('mutants:mutant-update', kwargs=kwargs), data
        )
        self.assertIn('backstory', response.context)
        self.assertFalse(response.context['form'].errors)
        self.assertEqual(response.status_code, 302)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • This test works as expected - is there anywhere to read up on how to better 'unit test' the view? Obviously I'm missing som key pieces, but I appreciate your help! – UpDawg Nov 03 '22 at 19:05