Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PATCH and PUT don't work as expected when pytest is interacting with REST framework

I am building an API using the django REST framework.

To test this API I am using pytest and the test client like so:

def test_doesnt_find(self, client):
    resp = client.post(self.url, data={'name': '123'})
    assert resp.status_code == 404

or

def test_doesnt_find(self, client):
    resp = client.get(self.url, data={'name': '123'})
    assert resp.status_code == 404

both work when using the general GET, POST and DELETE Classes of the REST framework (like DestroyAPIView, RetrieveUpdateAPIView or just APIView using get and post functions)

Where I get problems is when using PATCH and PUT views. Such as RetrieveUpdateAPIView. Here I suddenly have to use:

resp = client.patch(self.url, data="name=123", content_type='application/x-www-form-urlencoded')

or

resp = client.patch(self.url, data=json.dumps({'name': '123'}), content_type='application/json')

If I simply try to use the test client like I am used to, I get errors:

rest_framework.exceptions.UnsupportedMediaType: Unsupported media type "application/octet-stream" in request.

And when I specify 'application/json' in the client.patch() call:

rest_framework.exceptions.ParseError: JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)`

Can anyone explain this behavior to me? It is especially hard to catch as curl simply works as well using -X PATCH -d"name=123".

like image 891
David Schumann Avatar asked Oct 06 '16 23:10

David Schumann


2 Answers

Pytest uses the django test client and client.post default content_type is multipart/form-data, while put, patch and delete use application/octet-stream.

That's why this is tricky sometimes. Even with post requests, if you plan to support JSON payload, you must tell the content type in the test request. Anyway, with recent Django versions, you can just pass the data object to the client request and it will be serialized for you, as declared in the docs:

If you provide content_type as application/json, the data is serialized using json.dumps() if it’s a dict, list, or tuple. Serialization is performed with DjangoJSONEncoder by default, and can be overridden by providing a json_encoder argument to Client. This serialization also happens for put(), patch(), and delete() requests.

For example:

resp = client.patch(self.url, {'name': '123'}, content_type='application/json')
like image 77
Michel Sabchuk Avatar answered Nov 15 '22 17:11

Michel Sabchuk


rest_framework.exceptions.ParseError: JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)`

This is usually sign that you send a string inside a string in json. For example:

resp = client.patch(self.url, data=json.dumps("name=123"), content_type='application/json')

will cause this kind of issues.

rest_framework.exceptions.UnsupportedMediaType: Unsupported media type "application/octet-stream" in request.

This means that the request has been sent as "application/octet-stream" which is Django's test default.

To ease the pain with dealing with all that, Django REST framework provides a client on its own: http://www.django-rest-framework.org/api-guide/testing/#apiclient

Note that the syntax is slightly different from Django's one and that you won't have to deal with json encoding.

like image 36
Linovia Avatar answered Nov 15 '22 17:11

Linovia