Home > Net >  Django testing UpdateView - object has no attribute 'object'
Django testing UpdateView - object has no attribute 'object'

Time:11-04

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.

CodePudding user response:

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)
  • Related