Search code examples
pythondjangotestingautomated-testscomparison

Django: assertEquals with visually equal result with QuerySet


Can anybody explain me why these have the same values, but don't compare equal? Having a bit of a hard time to understand that. And what would be the best way to make them be "equal"?

Inspection using ipdb:

ipdb> expected_value                                                                                                                                  
{'author': 'john', 'id': 1, 'title': 'Yesterday', 'body': 'All my troubles seemed so far away', 'tags': <QuerySet ['Music', 'Lyrics']>}
ipdb> response                                                                                                                                        
{'author': 'john', 'id': 1, 'title': 'Yesterday', 'body': 'All my troubles seemed so far away', 'tags': <QuerySet ['Music', 'Lyrics']>}

Pieces of code:

class TestPost(TestCase):                                                                                               

    def test_serialize(self):                                                                                           
        user = User.objects.create(username="john", password="12345678", email="[email protected]")                      
        tag1 = Tag.objects.create(value="Music")                                                                        
        tag2 = Tag.objects.create(value="Lyrics")                                                                       
        post = Post.objects.create(author=user, title="Yesterday", body="All my troubles seemed so far away")           
        post.tags.add(tag1)                                                                                             
        post.tags.add(tag2)                                                                                             

        expected_value = {                                                                                              
            "author": user.username,                                                                                    
            "id": user.id,                                                                                              
            "title": post.title,                                                                                        
            "body": post.body,                                                                                          
            "tags": post.tags.all().values_list("value", flat=True),                                                    
        }                                                                                                               
        response = post.serialize()                                                                                                                                      
        self.assertEqual(expected_value, response)

and the Django model that I was testing

class Post(models.Model):                                                                                               
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=False, null=False,)            
    title = models.CharField(blank=False, null=False, validators=[MinLengthValidator(3)], max_length=50)                
    body = models.CharField(blank=False, null=False, validators=[MinLengthValidator(5)], max_length=1000)               
    tags = models.ManyToManyField("Tag")                                                                                

    def serialize(self):                                                                                                
        return {                                                                                                        
            "author": self.author.username,                                                                             
            "id": self.id,                                                                                              
            "title": self.title,                                                                                        
            "body": self.body,                                                                                          
            "tags": self.tags.all().values_list("value", flat=True),                                                    
        } 

Solution

  • Can anybody explain me why these have the same values, but don't compare equal?

    Because two QuerySet objects [GitHub] are not considered equal when they "carry" the same values. Only if the object itself is the same, it will be considered equal. Indeed, if you test this in the shell, you retrieve:

    >>> MyModel.objects.all() == MyModel.objects.all()
    False
    >>> qs = MyModel.objects.all()
    >>> qs == qs
    True
    

    After all a QuerySet is not only a collection of values, but also a query, and checking if two queries are equivalent, is probably an undeciable problem [wiki].

    You thus can convert this to a list with list(…), for example:

    expected_value = {
        'author': user.username,
        'id': user.id,
        'title': post.title,
        'body': post.body,
        'tags': list(post.tags.values_list('value', flat=True)),
    }

    as well as in the serializer:

    class Post(models.Model):
        author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
        title = models.CharField(validators=[MinLengthValidator(3)], max_length=50)
        body = models.CharField(validators=[MinLengthValidator(5)], max_length=1000)
        tags = models.ManyToManyField("Tag")
    
        def serialize(self):
            return {
                'author': self.author.username,
                'id': self.id,
                'title': self.title,
                'body': self.body,
                'tags': list(self.tags.values_list('value', flat=True))
            }