0

I am trying to test my Django application to get 100 % statement coverage.

I Am using class-based view and overwriting some af the functionalities. One of them is the form valid in my AttendanceLogFormView in my views.py

My question is how do I test that this method is working as it should with unit tests in Django? I am very new to testing so I very difficult for me to wrap me head around the concept I just know I need to test the if/else for statement coverage - but I don't know how?

class AttendanceLogFormView(CreateView):
    model = AttendanceLog
    template_name = "attendancecode/Createattendancelog.html"
    form_class = AttendanceLogForm
    success_url = "/attendancecode/success/"

    # Checks if data input is valid and saves object
    def form_valid(self, form):
        obj = form.save(commit=False)
        user = "nadi6548"
        obj.date = date.today()
        getClass = Class.objects.get(name=obj.keaclass)
        getCourse = Course.objects.get(name=getClass.Course_name)
        getLocation = School.objects.get(id=getCourse.location_id)
        coords_1 = (getLocation.lat, getLocation.long)
        coords_2 = (obj.lat, obj.long)
        # check location and that student goes in the class
        if (geopy.distance.distance(coords_1, coords_2).km < 0.5) and Student.objects.get(
                username=user, Class_id=obj.keaclass):
            # check log is a code with correct info and correct date and that
            # student has subject + code is active
            if AttendanceCode.objects.filter(
                    code=obj.attendanceCode,
                    keaclass_id=obj.keaclass,
                    subject_id=obj.subject_id,
                    date=obj.date,
                    isActive="True") and StudentHasSubject.objects.get(
                    student_name_id=user,
                    subject_name_id=obj.subject_id):
                obj.username_fk = user
                obj.save()
                return super().form_valid(form)
            else:
                return render(self.request, './attendancecode/error.html')
        else:
            return render(self.request, './attendancecode/error.html')

Update:

So I found out I can test the model is accepting the create by doing this:

    def test_Attendancelog_create(self):
        attendancelog = AttendanceLog.objects.create(attendanceCode= -9144444, keaclass= self.Class, subject=self.Subject)
        response = self.client.get(reverse('create attendance log'), kwargs={'attendanceCode':attendancelog.attendanceCode})
        self.assertEqual(response.status_code, 200)
        print(AttendanceLog.objects.last())
        self.assertEqual(AttendanceLog.objects.last().date, date.today())

But I want to test the if statements - so does the Attendance log not pass the if statements it should not be created. - so how do I test this functionality?

Nadia Hansen
  • 697
  • 2
  • 6
  • 16
  • You have to change the inputs of your test or tests to go down the different paths of your code. You might have to write many tests to reach full coverage of just the code you've posted. – Ross Rogers Dec 16 '21 at 23:05
  • but how do I write the test? and what do I assert when testing? – Nadia Hansen Dec 16 '21 at 23:29
  • https://docs.djangoproject.com/en/3.2/topics/testing/ What should the code do? That's what you should test. Assert that it does what you intended. – Ross Rogers Dec 16 '21 at 23:55
  • I have added what I have tried to the question - so now I test the model or something but I do not get the checks in the form valid function do I? – Nadia Hansen Dec 17 '21 at 00:23
  • The whole theory of testing on one foot is AAA: arrange the environment for the test in the test function, then act some action, then assert that what happened is what you wanted. – Lior Pollak Dec 17 '21 at 10:07

1 Answers1

0

One way is to test the view with the test client. Create a dict to POST to the view, that will take the error path, and then test that the output contains something distinctive from ./attendancecode/error.html

Something like

def test0035(self): 
    c = Client()
    login_ok = c.login(username='shipping', password='test') # failed?
    self.assertTrue( login_ok)        

    view_url = ...
    expected_in_output = 'A1b2c9'
    data = {
        'key':'value', ...
        # stuff that will satisfy form.is_valid(), but fail the tests
        # in your form_valid method
    } 
    response=c.post( view_url, data)   
    self.assertIn( expected_in_output, response.content.decode() )

If there's nothing sufficiently distinctive in that template, you can add an html comment for testing. E.g. <!-- foo.test0035 A1b2c9, leave this alone --> and you might also render a value into that comment foo={{something.foo}} if its not going to impact on production efficiency to do so.

It occurs to me that what you are doing might be better coded by converting a form with no errors into a form with errors and returning form_invalid, as in

if not ( (geopy.distance.distance(coords_1, coords_2).km < 0.5) and Student.objects.get(
            username=user, Class_id=obj.keaclass) ):

    form.add_error('fieldname', 'Error message') 
      # fieldname=None for a non_field error
    return self.form_invalid( form)
nigel222
  • 7,582
  • 1
  • 14
  • 22