Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing a Django query set

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?

like image 845
Vaughan Avatar asked Nov 13 '13 23:11

Vaughan


People also ask

How do you write unit tests in Django?

To write a test you derive from any of the Django (or unittest) test base classes (SimpleTestCase, TransactionTestCase, TestCase, LiveServerTestCase) and then write separate methods to check that specific functionality works as expected (tests use "assert" methods to test that expressions result in True or False values ...

Does Django use unittest?

Writing testsDjango's unit tests use a Python standard library module: unittest . This module defines tests using a class-based approach. When you run your tests, the default behavior of the test utility is to find all the test cases (that is, subclasses of unittest.

Is Pytest better than unittest?

Which is better – pytest or unittest? Although both the frameworks are great for performing testing in python, pytest is easier to work with. The code in pytest is simple, compact, and efficient. For unittest, we will have to import modules, create a class and define the testing functions within that class.


2 Answers

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.

like image 162
Piotr Pęczek Avatar answered Oct 04 '22 07:10

Piotr Pęczek


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).

like image 40
Simeon Visser Avatar answered Oct 04 '22 05:10

Simeon Visser