Unit testing a Django query set
Asked Answered
L

3

9

I'm trying to learn unit testing with Django/unittest.

These are simple versions of my models:

class Device(models.Model):
    name = models.CharField(max_length=100)

    def get_ips(self):
        return DeviceIP.objects.filter(device=self.id)


class DeviceIP(models.Model):
    ip = models.GenericIPAddressField()
    device = models.ForeignKey(Device)

And this is the test code I've come up with:

from django.test import TestCase

class DeviceTest(TestCase):

    def test_get_ips(self):
        device = Device()
        device.name = 'My Device'

        ip1 = DeviceIP()
        ip1.ip = '127.0.0.1'
        ip1.device = device
        ip1.save()

        ip2 = DeviceIP()
        ip2.ip = '127.0.0.2'
        ip2.device = device
        ip2.save()

        ip3 = DeviceIP()
        ip3.ip = '127.0.0.3'
        ip3.device = device
        ip3.save()

        self.assertEqual(device.get_ips(), [ip1, ip2, ip3])

The test results fails because on an AssertionError even though the string representations of device.get_ips() and [ip1, ip2, ip3] are identical.

If I try using self.assertListEqual I get an error because device.get_ips() is a QuerySet and not a list.

If I try self.assertQuerySetEqual I get an error saying "DeviceTest object has no attribute assertQuerySetEqual" but I'm not sure why because DeviceTest extends django.test's TestCase.

How should I be doing a test like this?

Also, in a "real" project would it make sense to do such a simple test?

Lamblike answered 13/11, 2013 at 23:5 Comment(2)
Are you aware that using device.deviceip_set.all() is Django's built in way to do what your get_ips() method does? Look at Django's documentation for reverse foreign keys. You also don't need to test it then, as it's standard Django behaviour.Niche
Thanks @Ben, I've only just started learning Django and wasn't aware of that. I'll switch to using that approach.Lamblike
O
7

The call device.get_ips() returns a QuerySet whereas [ip1, ip2, ip3] is a list. Hence they're currently not equal.

Given that you don't want to test things that may not matter (order in which rows are returned in .filter() from the database), I suggest testing as follows:

results = device.get_ips()
result_ips = [ip.ip for ip in results]
self.assertEqual(len(results), 3)
self.assertTrue(ip1.ip in result_ips)
self.assertTrue(ip2.ip in result_ips)
self.assertTrue(ip3.ip in result_ips)

This tests: three results and IPs are the same. This should give reasonable confidence that you're getting the same objects (although you can add more assertions as desired).

Otiose answered 13/11, 2013 at 23:17 Comment(2)
Or since the inputs are unique, just apply sorting in the assert which will take care of both list length and contents. self.assertEqual(sorted(result_ips), sorted([ip1,ip, ip2.ip, ip3.ip]))Limbic
Thanks for the answer and the comment which both worked for me. I'm going to change to using deviceip_set() as Ben recommended above, but this is exactly what I needed to understand how to approach this type of testing. @AustinPhillips, minor typo: ip1,ip should be ip1.ipLamblike
U
10

Actually the right way, and recommended by djangoproject is:

    self.assertEqual(list(device.get_ips()), [ip1, ip2, ip3])

Forcing sorted on queryset and list will change your testing scenario and you don't want it.

Uri answered 18/4, 2016 at 10:35 Comment(0)
O
7

The call device.get_ips() returns a QuerySet whereas [ip1, ip2, ip3] is a list. Hence they're currently not equal.

Given that you don't want to test things that may not matter (order in which rows are returned in .filter() from the database), I suggest testing as follows:

results = device.get_ips()
result_ips = [ip.ip for ip in results]
self.assertEqual(len(results), 3)
self.assertTrue(ip1.ip in result_ips)
self.assertTrue(ip2.ip in result_ips)
self.assertTrue(ip3.ip in result_ips)

This tests: three results and IPs are the same. This should give reasonable confidence that you're getting the same objects (although you can add more assertions as desired).

Otiose answered 13/11, 2013 at 23:17 Comment(2)
Or since the inputs are unique, just apply sorting in the assert which will take care of both list length and contents. self.assertEqual(sorted(result_ips), sorted([ip1,ip, ip2.ip, ip3.ip]))Limbic
Thanks for the answer and the comment which both worked for me. I'm going to change to using deviceip_set() as Ben recommended above, but this is exactly what I needed to understand how to approach this type of testing. @AustinPhillips, minor typo: ip1,ip should be ip1.ipLamblike
H
1

You typed self.assertQuerySetEqual and it should be self.assertQuerysetEqual, try:

self.assertQuerysetEqual(device.get_ips(), [repr(ip1), repr(ip2), repr(ip3)], 
                         ordered=False)

Or if you still want to test it against a list:

self.assertItemsEqual(device.get_ips(), [ip1, ip2, ip3])
Horde answered 3/12, 2018 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.