Search code examples
djangodjango-testing

Django - Are all relevent models needed when testing the URL of a functional view?


I have several functional views that are passed parameters. I'm trying to write tests to check the url, status, etc. So far I'm starting with just testing the URL name. I have some querysets in the view and it looks from the traceback that I need to define objects for the queries in my setUp

urls

app_name = 'gradebook'
urlpatterns = [     
    path('updatesinglegrade/<int:assess_pk>/<int:class_pk>/<int:grade_pk>/',
             views.updatesinglegrade, name='updatesinglegrade'),
]

view

def updatesinglegrade(request, assess_pk, class_pk, grade_pk):
    grade = Grade.objects.get(id=grade_pk)
    gradescale = GradeBookSetup.objects.get(user=request.user)
    scale_choice = grade_scale_choice(gradescale.scale_mode)

    form = UpdateGradeForm(gradescale=scale_choice)
    context = {'form': form}
    context['grade'] = grade

    if request.method == 'POST':
        form = UpdateGradeForm(
            request.POST, gradescale=scale_choice)

        if form.is_valid():
            cd = form.cleaned_data
            grade.score = cd['score']
            grade.save()
            return redirect('gradebook:assessdetail', assess_pk, class_pk)

        else:
            return render(request, "gradebook/grade_single_form.html", context)

    else:
        return render(request, "gradebook/grade_single_form.html", context)

test

class UpdateSingleGradeTests(TestCase):

    def setUp(self):
        self.user = CustomUser.objects.create_user(
            username='tester',
            email='[email protected]',
            password='tester123'
        )
        login = self.client.login(username='tester', password='tester123')

    def test_updatesinglegrade_url_name(self):
        response = self.client.get(reverse('gradebook:updatesinglegrade', kwargs={
                                   'assess_pk': 1, 'class_pk': 2, 'grade_pk': 3}))
        self.assertEqual(response.status_code, 200)

traceback

======================================================================
ERROR: test_updatesinglegrade_url_name (gradebook.tests.UpdateSingleGradeTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Doug\OneDrive\django\gradebook\gradebook\tests.py", line 102, in test_updatesinglegrade_url_name
    response = self.client.get(reverse('gradebook:updatesinglegrade', kwargs={
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\test\client.py", line 742, in get
    response = super().get(path, data=data, secure=secure, **extra)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\test\client.py", line 396, in get
    return self.generic('GET', path, secure=secure, **{
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\test\client.py", line 473, in generic
    return self.request(**r)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\test\client.py", line 719, in request
    self.check_exception(response)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\test\client.py", line 580, in check_exception
    raise exc_value
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\contrib\auth\decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\contrib\auth\decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "C:\Users\Doug\OneDrive\django\gradebook\gradebook\views.py", line 1030, in updatesinglegrade
    grade = Grade.objects.get(id=grade_pk)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\Doug\.virtualenvs\gradebook-6RQSg7dk\lib\site-packages\django\db\models\query.py", line 435, in get
    raise self.model.DoesNotExist(
gradebook.models.Grade.DoesNotExist: Grade matching query does not exist.

----------------------------------------------------------------------
Ran 6 tests in 1.527s

FAILED (errors=1)

I can create a Grade object as the traceback indicates, but my Grade model depends on five ForeignKeys, one of which has another ForeignKey:

model

class Grade(models.Model):
    score = models.CharField(max_length=3, blank=True, default="INC")
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    assessment = models.ForeignKey(
        Assessment, on_delete=models.CASCADE, null=True, blank=True)
    objective = models.ForeignKey(
        Objective, on_delete=models.CASCADE, blank=True)
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    cblock = models.ForeignKey(Classroom, on_delete=models.CASCADE, default=1)
    time_created = models.DateField(
        auto_now=False, auto_now_add=False, default=timezone.now)

working test

from .models import CustomUser, Grade, Student, Course, Classroom, Objective, Assessment, GradeBookSetup

class UpdateSingleGradeTests(TestCase):

    def setUp(self):
        self.user = CustomUser.objects.create_user(
            username='tester',
            email='[email protected]',
            password='tester123',
            is_teacher=True,
        )
        studentuser = CustomUser.objects.create(
            username='student_test'
        )
        stud = Student.objects.create(
            user=studentuser,
            student_number='111222'
        )
        course = Course.objects.create(
            user=self.user
        )
        classroom = Classroom.objects.create(
            user=self.user,
            course=course
        )
        # classroom.students.set(stud)
        objective = Objective.objects.create(
            user=self.user,
            course=course
        )
        assessment = Assessment.objects.create(
            user=self.user,
            course=course
        )
        # assessment.objectives.set(objective)
        login = self.client.login(username='tester', password='tester123')

        grade = Grade.objects.create(
            id=3,
            user=self.user,
            student=stud,
            objective=objective,
            cblock=classroom,
            assessment=assessment
        )

        gbs = GradeBookSetup.objects.create(
            id=1,
            user=self.user
        )

    def test_updatesinglegrade_url_name(self):
        response = self.client.get(reverse('gradebook:updatesinglegrade', kwargs={
            'assess_pk': 1, 'class_pk': 2, 'grade_pk': 3}))
        self.assertEqual(response.status_code, 200)

Does this mean that I need to create 6 objects in the setUp? I got it work for this test, and I'm wondering if this is something I will have to add to many of my class TestCase (which have similar objects/models involved)? Or perhaps there is a more efficient way of doing this?


Solution

  • The short answer is, yes, non-null ForeignKey fields must be populated in tests because they are non-null.

    There are several ways to do this:

    1. Call objects.create() on each model. This can be very tedious and time consuming.

    2. Use a fixture. Creating the JSON files can also be tedious, but fortunately, you can use the Django admin to do data entry and then ./manage.py dumpdata to generate the JSON.

    3. Use a 3rd party library such as factory-boy or model-bakery. These libraries can generate random data for fields that don't really matter for testing other than that they have a value. If you need a specific value for a certain test, then you can provide it.