Search code examples
pythondjangodjango-formsdjango-testing

Django TestCase on form validation fails even tough input is valid


As in the title described I've got a form which validates correctly when I am using the FormView. However as I started writting tests today the same input fails in the TestCase and I get the following error:

{'programming_language': ['Select a valid choice. That choice is not one of the available choices.']}

These are the models, forms, views and the test I am using

# models.py
from django.db import models


class Tag(models.Model):

    name = models.CharField(max_length=40, unique=True)

class ProgrammingLanguage(models.Model):

    name = models.CharField(max_length=40, unique=True)

class Snippet(models.Model):

    title = models.CharField(max_length=40)
    programming_language = models.ForeignKey(ProgrammingLanguage, on_delete=models.CASCADE)
    creation_date = models.DateTimeField(auto_now_add=True)
    explanation = models.TextField()
    code = models.TextField()
    tags = models.ManyToManyField(Tag)


# forms.py  
from django import forms
from django.utils.translation import gettext_lazy as _

from .models import Snippet

class SnippetForm(forms.ModelForm):

    class Meta:
        model = Snippet
        exclude = ["creation_date"]

# views.py
from django.urls import reverse_lazy
from django.views import generic
from .models import Snippet
from .forms import SnippetForm

class SnippetFormView(generic.FormView):
    template_name = "snippets/snippet_form.html"
    form_class = SnippetForm
    success_url = reverse_lazy("snippets")

    def form_valid(self, form):
        # for testing purposes
        print(form.cleaned_data)
        form.save()
        return super().form_valid(form)

# test_forms.py
from django.test import TestCase

from snippets.forms import SnippetForm
from snippets.models import ProgrammingLanguage, Tag, Snippet

class SnippetFormTestCase(TestCase):

    @classmethod
    def setUpTestData(cls):
        ProgrammingLanguage.objects.create(name="Javascript")
        Tag.objects.create(name="website")

    def test_forms(self):
        form = SnippetForm({
            'title': 'Test snippet title',
            'programming_language': ProgrammingLanguage.objects.get(pk=1),
            'code': 'code here',
            'explanation': 'explanation here',
            'tags': Tag.objects.all()
        })
        # calling is valid to get cleaned_data and original data 
        form.is_valid()
        print(form.data)
        print(form.cleaned_data)
        self.assertEqual(form.errors, {})

This is the cleaned_data I get in the view:

{'title': 'Test snippet title', 'programming_language': <ProgrammingLanguage: Javascript>, 'explanation': 'explanation here', 'code': 'code here', 'tags': <QuerySet [<Tag: website>]>}

And this is the data and the cleaned_data I get during the test:

# data
{'title': 'Test snippet title', 'programming_language': <ProgrammingLanguage: Javascript>, 'code': 'code here', 'explanation': 'explanation here', 'tags': <QuerySet [<Tag: website>]>}
# cleaned_data
{'title': 'Test snippet title', 'explanation': 'explanation here', 'code': 'code here', 'tags': <QuerySet [<Tag: website>]>}

Why is the ProgrammingLanguage an invalid choice during the test, but valid while using the website?


Solution

  • Try this in your test:

    form = SnippetForm({
            'title': 'Test snippet title',
            'programming_language': 1, #ProgrammingLanguage.objects.get(pk=1),
            'code': 'code here',
            'explanation': 'explanation here',
            'tags': Tag.objects.all()
        })
    

    When using a form for a model that has a primary key as one of its fields, you bound the form to data using the pk value for such field, not the model instance.

    This is what FormView does under the hood. And that's why your test is failing while the view works ok.

    also be sure you have a programming language which pk is 1.