pythondjangodjango-rest-frameworkdjango-tests

Elegant way to add a list view test case in drf


I have one concern in writing the test code for list view.

For DRF's list view, it responds to multiple data.

What should I do if I assume that the response.data object is a model with many fields?

response.data --> [{"id": 1, "field1": "xxx", "field2": "yyy"....}, {"id": 2, ...}, ...]

Too much input is required to compare with static data when a returns a lot of field data.

def test_xx(self):
    self.assertEqual(response.data, [{"id": 1, "field1": "xxx", ..., "field12": "ddd", ...}, ...])

Currently, the way I came up with is as follows.

I created an example model called Dog.

class Dog(models.Model):
    ...
    hungry = models.BooleanField(default=False)
    hurt = models.BooleanField(default=False)

As an example, let's say there is a class called Dog and there is logic in View to get a list of hungry dogs. I'm going to make a test about the logic of get a list of hungry dogs.

class ...
    def test_dog_hungry_list(self):
        response = client.get("/api/dog/hungry/")
        expected_response = Dog.objects.filter(hungry=True)
        self.assertEqual(response.data, expected_response)

The two values that I want to compare with assertEqual, response and expected_response, are of different types.

In the case of response.data, Serializer is applied, so when it's in dict form, I want to compare it with Queryset type expected_response.

I thought of two ways as follows.

  1. Apply serializer to Queryset for comparison. --> (Serializer(expected_response, many=True))
  2. Compare the primary key values.

number 2 example.

expected_response = Dog.objects.filter(hungry=True).values("id")
self.assertEqual(
[data["id"] for data in response.data],
[data["id"] for data in expected_response]
)

But I'm not sure if the two methods are good in comparison.

Is there a way to make the data smart to compare when the test code returns two or three data in the form of a large dict?


Solution

  • Correct approach is really depends on your list View. If you testing a simple serialiser work, like this one:

    class DogSerializer(serializers.ModelSerializer):
        class Meta:
            model = Dog
            fields = ['id', 'name', 'age', 'hungry', 'hurt']
    

    With simple view like this one:

    class HungryDogListView(ListAPIView):
        queryset = Dog.objects.filter(hungry=True)
        serializer_class = DogSerializer
    

    You could just check that response is 200_OK and needed ids in your list:

    def test_dog_hungry_list(self):
        response = self.client.get("/api/dog/hungry/")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        expected_ids = Dog.objects.filter(hungry=True).values_list("id", flat=True)
        response_ids = [dog['id'] for dog in response.data]
        self.assertEqual(set(response_ids), set(expected_ids))
    

    But if you have tricky serialiser with some SerializerMethodField, you should check not only ids but correct calculation of these fields:

    class DogSerializer(serializers.ModelSerializer):
        is_injured_or_hungry = serializers.SerializerMethodField()
        age_in_dog_years = serializers.SerializerMethodField()
    
        class Meta:
            model = Dog
            fields = ['id','name', 'age', 'hungry', 'hurt', 'is_injured_or_hungry','age_in_dog_years']
        
        def get_is_injured_or_hungry(self, obj):
            return obj.hungry or obj.hurt
    
        def get_age_in_dog_years(self, obj):
            return obj.age * 7
    

    In this example best practice is to check is_injured_or_hungry and age_in_dog_years

    Also you could have some special logic in your View or even in your middleware, that should be checked.

    So there is no simple answer. All depends on your specific case