Django testing UpdateView - object has no attribute 'object'

483 Views Asked by At

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.

1

There are 1 best solutions below

1
willeM_ Van Onsem On BEST ANSWER

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)