Skip to content

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

Closed
@jlandercy

Description

@jlandercy

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

class 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.py

class 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.py

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions