Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Unexpected different behavior between exposed API and Unit Test APIClient request#9703

Answeredbyjlandercy
jlandercy asked this question inGeneral
Discussion options

My use case is the following: I have Items that have Properties. There is a Many 2 Many relationship between them. I want to be able to create Items with Properties that are created on the fly. I mean if property exists it is fetched, if it does not it is created. I can do so using the following pattern:

# models.pyclass Property(models.Model):    name = models.CharField(max_length=64, unique=True)class Item(models.Model):    name = models.CharField(max_length=64, unique=True)    properties = models.ManyToManyField(Property, through="ItemProperty")class ItemProperty(models.Model):    property = models.ForeignKey(Property, on_delete=models.CASCADE)    item = models.ForeignKey(Item, on_delete=models.CASCADE)class PropertySerializer(serializers.Serializer):    name = serializers.CharField()# serializer.pyclass ItemSerializer(serializers.ModelSerializer):    properties = PropertySerializer(many=True, required=False)    class Meta:        model = models.Item        fields = "__all__"    def create(self, validated_data):        data = copy.deepcopy(validated_data)        properties = data.pop("properties", [])        item = models.Item.objects.create(**data)        for property in properties:            current_property, _ = models.Property.objects.get_or_create(**property)            item.properties.add(current_property)        return item# views.pyclass PropertyViewSet(viewsets.ModelViewSet):    queryset = models.Property.objects.all()    serializer_class = serializers.PropertySerializer    permissions_classes = [permissions.IsAuthenticated]class ItemViewSet(viewsets.ModelViewSet):    queryset = models.Item.objects.all()    serializer_class = serializers.ItemSerializer    permissions_classes = [permissions.IsAuthenticated]router.register('property', PropertyViewSet, 'property')router.register('item', ItemViewSet, 'item')

With this setup, when I perform a POST request with the following payload on the API directly (using Swagger):

{        "name": "test",        "properties": [{"name": "test"}, {"name": "dummy"}]}

I get a 201 with the following body:

{  "id": 3,  "properties": [    {      "name": "test"    },    {      "name": "dummy"    }  ],  "name": "test"}

Which is the expected result (property are created on the fly). Now if I want to automatize this check using Unit Test, I'll do something like this:

class TestAPIItemsAndProperties(APITestCase):    fixtures = ["core/fixtures/users.yaml"]    payload = {        "name": "test",        "properties": [{"name": "test"}, {"name": "dummy"}],    }    def setUp(self):        self.user = models.CustomUser.objects.get(username="jlandercy")        self.client = APIClient()        self.client.force_authenticate(user=self.user)    def test_complete_payload_is_sent(self):        response = self.client.post("/api/core/item/", data=self.payload)        print(response)        print(response.json())

And then I get a 201 where properties are totally ignored (seems like they are popped):

{'id': 1, 'properties': [], 'name': 'test'}

I wonder if I am doing something wrong in automatizing test or if it is potentially a bug.

You must be logged in to vote

It seems with theformat switch it works:

        response = self.client.post("/api/core/item/", data=self.payload, format="json")

But withContent-Type defined we need to encode the JSON:

        response = self.client.post("/api/core/item/", data=json.dumps(self.payload), content_type="application/json")

Replies: 1 comment 6 replies

Comment options

May I ask why not being able to reproduce the API behaviour in Unit Test is just a discussion and not an issue ?

You must be logged in to vote
6 replies
@browniebroke
Comment options

This is generally the process in this project: start with a discussion and let maintainers convert to an issue if applicable. Granted, it's not well documented at the moment (it was removed inhttps://github.com/encode/django-rest-framework/pull/9660/files#diff-e47c48e8f0317724062520e87f0d688e0b80ab197d4ac083445782243caae14fL33 and not restored), but this is it.

The rationale is that DRF user base is so big that most changes (even minor fixes) can be considered breaking changes and should be avoided unless absolutely necessary. Another way of saying that is that all currently exhibited behaviour are most likely considered "expected" by a vast majority of our users and are often best addressed by adjusting the user understanding. Leaving a discussion open is more visible and helpful to others facing the same problem than a closed issue.

I'm not saying your issue isdefinitely not a bug, but it's more of a case of "not a bug until proven to be".

@jlandercy
Comment options

Thank you for answering@browniebroke, so I can understand why it has been converted into a discussion. Meanwhile I have the feeling no one had time to try the MCVE I have written. Fair enough, we all are busy.

But what about the fact I clearly state Unit Test are not able to reproduce the actual behavior of the API. To my understanding, Unit Testes are important and the confidence in reproducing the actual behavior of the piece of code it does test is crucial. How can I trust DRF package if I am not able to write successful Unit Test ?

I am a bit disappointed about the sentence "not a bug until proven to be". Well, what do I need to do to prove it is a bug? Isn't the MCVE clear enough? Does it lack debugging details?

@browniebroke
Comment options

Try setting the Content-Type request header to JSON?

-        response = self.client.post("/api/core/item/", data=self.payload)+        response = self.client.post("/api/core/item/", data=self.payload, content_type="application/json")
@jlandercy
Comment options

It seems with theformat switch it works:

        response = self.client.post("/api/core/item/", data=self.payload, format="json")

But withContent-Type defined we need to encode the JSON:

        response = self.client.post("/api/core/item/", data=json.dumps(self.payload), content_type="application/json")
Answer selected byjlandercy
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
General
Labels
None yet
3 participants
@jlandercy@tomchristie@browniebroke
Converted from issue

This discussion was converted from issue #9702 on May 19, 2025 16:47.


[8]ページ先頭

©2009-2025 Movatter.jp