comparing querysets in django TestCase
Asked Answered
P

8

31

I have a very simple view as follows

def simple_view(request):
    documents = request.user.document_set.all()
    return render(request, 'simple.html', {'documents': documents})

To test the above view in my test case i have the following method which errors out.

Class SomeTestCase(TestCase):
    # ...
    def test_simple_view(self):
        # ... some other checks
        docset = self.resonse.context['documents']
        self.assertTrue(self.user.document_set.all() == docset) # This line raises an error
    # ...

The error i get is AssertionError: False is not true. I have tried printing both the querysets and both are absolutely identical. Why would it return False when both the objects are identical ? Any Ideas ?

Currently to overcome this, I am using a nasty hack of checking lengths as follows:

ds1, ds2 = self.response.context['documents'], self.user.document_set.all()
self.assertTrue(len([x for x in ds1 if x in ds2]) == len(ds1) == len(ds2)) # Makes sure each entry in ds1 exists in ds2
Pless answered 17/4, 2013 at 11:16 Comment(0)
H
43

The queryset objects will not be identical if they are the result of different queries even if they have the same values in their result (compare ds1.query and ds2.query).

If you convert the query set to a list first, you should be able to do a normal comparison (assuming they have the same sort order of course):

self.assertEqual(list(ds1), list(ds2))
Hierolatry answered 17/4, 2013 at 11:45 Comment(5)
Or slight more succinctly self.assertEqual(list(ds1), list(ds2))Marandamarasca
If your querysets are too huge for evaluating then you probably can use django's defer or onlySelfheal
Perhaps we could use set(), instead of list(), to be independent of sort-order.Carious
True, could put them in a set but that could also hide issues where one query set has duplicate rows while the other doesn't.Hierolatry
@MattiJohn: You're absolutely right. Actually I just ran into such an issue myself. I'll stick to list(). Thanks!Carious
C
13

This alternative doesn't need sorting:

self.assertQuerysetEqual(qs1, list(qs2), ordered=False)

See the assert reference.

Note: Only for django 1.4+.

Confidant answered 17/4, 2013 at 13:31 Comment(4)
Tried this, but querysets are not equals all the time (even if they are the same)! Got it worked using: self.assertQuerysetEqual(qs1, map(repr, qs2), ordered=False). From this thread: https://mcmap.net/q/391441/-django-1-4-assertquerysetequal-how-to-use-methodUnorthodox
@Unorthodox I don't really know why... it's supposed to use repr() to compare by default. And if both quesysets are equal the repr is expected to be the same, right?Vang
Looking at docs i thought, that your example will work. But it doesn't... Looks like repr func is applied only to first non-list queryset, and for the second list it must be applided explicitly. With django 1.4.5: qs1 = M.objects.all(); qs2 = M.objects.all() # (the same), self.assertQuerysetEqual(qs1, list(qs2), ordered=False) <-- fails; self.assertQuerysetEqual(qs1, map(repr, qs2), ordered=False) <-- passes;Unorthodox
Yes assertQuerySetEquals fails in 2.1 even if the same queryset reference is passed e.g., assertQuerySetEquals(qs1, qs1) -- but stalk's solution does work.Simonesimoneau
B
2

You can use the assertQuerySetEqual method to compare two querysets without casting to a list. This will return True if the values of the querysets are equal, regardless of if the queries are different.

One gotcha is that this method applies the repr function as a transform to each object in the first argument before comparing, which can result in the following error when comparing two actually equal querysets:

AssertionError: Lists differ: ['<Object: id1'] != [<Object: id1>]

The solution is to pass your own transform function to use instead of repr:

self.assertQuerySetEqual(qs1, qs2, transform=lambda x: x)
Byelorussian answered 1/7, 2021 at 14:32 Comment(0)
D
2

if you want to check that Django querysets are completely the same

self.assertEqual(str(simple_qs.query), str(complex_qs.query))

Dyal answered 8/9, 2022 at 15:34 Comment(0)
B
1

For me works the transform option:

    def subject(self):
        return Mission.objects.add_keynest_api_token().filter(keynest_api_token__isnull=False)

    def test_mission_has_property(self):
        self.mission.appartement = self.property
        self.mission.save()
        self.assertQuerysetEqual(self.subject(), [self.mission], transform=lambda x: x)

Bestialize answered 4/10, 2019 at 10:26 Comment(0)
O
0

If you'd like to assert that the content of two querysets is equal without worrying about the order, use the following:

self.assertQuerysetEqual(actual_queryset, expected_queryset, transform=lambda x: x, ordered=False)
Ortega answered 16/6, 2022 at 16:7 Comment(0)
P
-1

Found a solution. We need to convert Querysets to sorted lists before we can compare them. Something as follows.

Class SomeTestCase(TestCase):
    # ...
    def test_simple_view(self):
        # ... some other checks
        docset1 = self.resonse.context['documents']
        docset2 = self.user.document_set.all()
        self.assertTrue(list(sorted(docset1)) == len(sorted(docset)))
    # ...
Pless answered 17/4, 2013 at 11:46 Comment(0)
A
-1
def test_simple_view(self):
    # ... some other checks
    docset = set([i.pk for i in self.resonse.context['documents']])
    prev = set([i.pk for i in self.user.document_set.all()])
    diff = prev.difference(docset)
    self.assertTrue(len(diff)==0)
Aneto answered 17/3, 2022 at 0:57 Comment(1)
See "Explaining entirely code-based answers". While this might be technically correct, it doesn't explain why it solves the problem or should be the selected answer. We should educate along with helping solve the problem.Grazia

© 2022 - 2024 — McMap. All rights reserved.