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

5

16

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

Buiron answered 6/10, 2016 at 23:6 Comment(2)
What's the test client you are referring to ? Django's ? DRF's ? another one ?Zoril
it is the django test client [pytest-django.readthedocs.io/en/latest/…Buiron
Z
25

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.

Zoril answered 10/10, 2016 at 8:11 Comment(2)
Tip: rather than instantiating an APIClient you can just inherit from APITestCase for your test classesCrashaw
resp = client.patch(self.url, json={"name": 123})Perpetual
T
3

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')
Torhert answered 17/7, 2020 at 14:29 Comment(0)
F
0

As for the request with JSON data you are receiving this error due to JSON syntax it needs double quotes over a string.

Fluorosis answered 7/10, 2016 at 18:6 Comment(0)
W
0

Late to the party. But for the Googlers, you should use format='json' in the django test client.

Manual json.dumping and setting content_type doesn't always work as expected.

resp = client.patch(self.url, data={'name': '123'}, format='json')
Wellfavored answered 5/4, 2022 at 9:11 Comment(0)
K
0

Try for put / patch for django TestCase class:

from django.test.client import encode_multipart

body = {
  'file': SimpleUploadedFile('file.xlsx', file_data, content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
  'other_field': ...,
}

self.client.put(f'some_api/{id}/', encode_multipart(body), format='multipart', content_type='multipart/form-data; boundary=BoUnDaRyStRiNg')

By default content_type is application/octet-stream. As mentioned boundary can be any value. Also check the docs for proper form-data formating. Here is the actual method that is causing the error if no boundary is specified.

Knickers answered 30/1 at 7:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.