Django Rest Framework APIClient does not parse query parameters - django

I am using djangorestframework==3.3.3 and Django==1.9.4
I have a test where I want to check that query parameters processed correctly.
class TestResourceView(APITestCase):
def test_view_process_query_params_correctly(self):
client = APIClient()
client.login(username='<username>', password='password')
response = client.get('/api/v2/resource/1/users/?fields=first_name;last_name')
self.assertEqual(response.status_code, 200)
# .... rest of the test ....
In my view I put print statement just to see if query parameters are parsed properly, but I get empty query dictionary:
class Resource(APIView):
def get(self, request):
query_params = request.query_params
print('Printing query params')
print(query_params)
# .... rest of the code ....
def post(self, request):
query_params = request.query_params
print('Printing query params')
print(query_params)
# .... rest of the code ....
Result in terminal when running tests:
Printing query params
<QueryDict: {}>
In the same time if I test post request like this:
response = client.post('/api/v2/resource/1/users/?fields=first_name;last_name')
i get params incorrectly parsed:
Printing query params
<QueryDict: {'last_name': [''], 'fields': ['first_name']}>
What is the correct way of using APIClient? Or is this still a bug? Because there was already similar issue

For me, the issue was that DRF wants the params in data not args like the Django test client.
This answer helped me:
https://stackoverflow.com/a/45183972/9512437
class AccountTests(APITestCase):
def setUp(self):
self.user = CustomUser.objects.create_user(email="user1#test.com", password="password1", is_staff=True)
self.client = APIClient()
def test_add_name(self):
self.client.force_authenticate(self.user)
url = reverse('customuser-detail', args=(self.user.id,))
data = {'first_name': 'test', 'last_name': 'user'}
response = self.client.put(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

The format of your query parameters is incorrect.
If you want the result to be {'fields': ['last_name', 'first_name']}, then your POST url should be .../users/?fields=first_name&fields=last_name'. You'll probably want to access the params using getlist().

1) Regarding empty <QueryDict: {}> in client.get('/api/v2/resource/1/users/?fields=first_name;last_name') - there was my mistake in my code. I had two tests with the same name, one of which indeed has empty <QueryDict: {}>. So, when running tests, django ran the test with <QueryDict: {}>
2) Regarding incorrect parsing of query params in client.post('/api/v2/resource/1/users/?fields=first_name;last_name'), I found the following discussion. So, django basically follows HTTP standards, where it is said that ; semicolon is reserved character and if using, than the correct way to comprehend it same as &. Here more details

Related

How to test django non-REST POST endpoint?

I'm having trouble testing one of my endpoints:
#require_http_methods(["POST"])
def store(request):
try:
body_unicode = request.body.decode('utf-8')
body = ast.literal_eval(body_unicode)
new_short_url = body['short_url']
original_url = body['original_url']
check_parameters(new_short_url, original_url)
Url.objects.create(short_url=new_short_url, original_url=original_url)
return HttpResponse('Created', status=201)
except KeyError as error:
return HttpResponse('Missing {}'.format(error.args), status=400)
except (AttributeError, IntegrityError, ValidationError) as error:
return HttpResponse(error.args, status=400)
As you can see, this endpoint only accepts POST requests and when trying to pass data from my tests, it arrives in the request.body, so I implemented my logic to get the data from there:
def test_create_url_ok(self):
creation_data = {
"short_url": "ab",
"original_url": "https://stackoverflow.com/"
}
response = self.client.post(reverse('store'), data=creation_data, content_type="application/json")
self.assertEqual(response.status_code, 201)
This works, but the problem is that when sending requests from my templates, data is not in the request.body, but in the request.POST. How to send data in the request.POST from my tests?
So, the thing is that vanilla django accepts data from request.POST when the content_type='multipart/form-data'. Since I was using 'json' it wasn't working (though it works when using DjangoRestFramework views):
When using default django tests, this content_type is the default, so you don't need to specify any:

DRF- Post Request Unitest

I have a post method under View Set. I need to write a unit test case for the method. when I pass param its give None. How should I pass both param and data(payload).
views.py
#action(detail=True, methods=['post'])
def complete_task(self, request, *args, **kwargs):
"""
Method for complete the task
input post request : task_id : str, variable_return:boolean, request data: dict
output Response : gives whether task is completed or not
"""
try:
get_task_id = self.request.query_params.get("task_id")
get_process_variables = request.data
print(get_task_id)
print(get_process_variables)
complete_task = CamundaWriteMixins.complete_task(url=CAMUNDA_URL, task_id=get_task_id,
process_variable_data=get_process_variables)
print("compl", complete_task)
return Response({"task_status": str(complete_task)})
except Exception as error:
return Response(error)
test.py
def test_completed_task(self):
self.client = Client()
url = reverse('complete-task')
data = {"variables": {
"dept_status": {"value": "approved", "type": "String"}}
}
response = self.client.post(url, data=data, params={"task_id": "000c29840512"},
headers={'Content-Type': 'application/json'})
print(response.data)
self.assertTrue(response.data)
I have tried above test case method which is getting request data but I got param None.
Thanks in Advance,.
if you just modify your request a bit and add query param as part of your url then i guess you are good to go.
Example:
response = self.client.post(f'{url}?task_id=000c29840512', data=data,
headers={'Content-Type': 'application/json'})
you can refer the official documentation for the example: https://docs.djangoproject.com/en/4.0/topics/testing/tools/

DRF Viewset test method

I have added a method to my viewset as follows:
class CustomImageViewSet(viewsets.ModelViewSet):
queryset = CustomImage.objects.all()
serializer_class = CustomImageSerializer
lookup_field = 'id'
#action(detail=True, methods=['get'], url_path='sepia/')
def sepia(self, request, id):
# do something
data = image_to_string(image)
return HttpResponse(data, content_type="image/png", status=status.HTTP_200_OK)
Since it is not a default or overridden request method, I am not sure how can I proceed writing a test for it. Any suggestions?
You're not clear on what the test should test but you can test the response status_code for example like this:
def test_sepia_api():
api_client = APIClient()
response = api_client.get(path="{path_to_your_api}/sepia/")
assert response.status_code == 200
I noticed you were using pytest. I'll assume you've got pytest-django too then (it really does make everything easier). I like using request factory since it's generally faster if you've got authentication needs.
def test_me(self, user, rf):
view = CustomImageViewSet()
request = rf.get("")
request.user = user # If you need authentication
view.request = request
response = view.sepia(request, 123)
assert response.data == BLAH

django send json patch request during test

I am using django 3.0. Here is my view that I am trying to test:
def myview(request):
values = {}
if request.method == 'PATCH':
keys = QueryDict(request.body)
print(keys)
for key in keys:
cache.set(key, keys[key], timeout=300)
values[key] = keys[key]
return JsonResponse(values, status=200)
and my test case:
class ValueViewTestCase(TestCase):
def setUp(self):
self.c = Client()
def test_value_updated(self):
data = {'key_1': 'updated_val'}
response = self.c.patch('/values/', data)
print(response.json())
# self.assertEqual(response.json(), data) # ->> test failing
console logs:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
<QueryDict: {'{"key_1": "updated_val"}': ['']}>
{'{"key_1": "updated_val"}': ''}
I want to send data as key value pair, but somehow it malformed , right now whole request acting as a key.
Your data format is wrong.
A querydict would not contain a json, but a sequence of request parameters like key_1=1&key_2=2&key3=3. Try this:
def test_value_updated(self):
data = 'key_1=1&key_2=2&key3=3'
response = self.c.patch('/values/', data)
print(response.json())
Hope this helps.

What is the best way to test drf serializer validate

I think two way how can i test drf serializer validate
following is my serializer validate code
def validate_md5(self, md5):
if len(md5) != 40:
raise serializers.ValidationError("Wrong md5")
return md5
and it is test code
1)
def test_wrong_validate_md5_2(self):
url = reverse('apk-list')
response = self.client.post(url, {'md5':'test'}, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
2)
def test_wrong_validate_md5(self):
serializer = ApkSerializer(data=self.apk)
if len(self.apk.get('md5')) != 40:
self.assertEqual(serializer.is_valid(), False)
else:
self.assertEqual(serializer.is_valid(), True)
what is better than another?
or is there best solution?
and ... I practice test-driven coding. is it necessary to write test code as above
The first method actually doesn't test serializer class. It's testing entire 'apk-list' endpoint. Since error may be raised not only in serializer's validate_md5 method, but in any other places even if self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) will be passed you cannot be sure that serializer worked as expected.
So second method is preferabe. But instead of if/else in one test caseyou'd better create two different test case: one for correct data another for incorrect and also you can check if error indead related with md5 field:
def test_wrong_validate_md5(self):
serializer = ApkSerializer(data=self.apk_wrong)
self.assertEqual(serializer.is_valid(), False)
self.assertEqual(set(serializer.errors.keys()), set(['md5']))
def test_correct_validate_md5(self):
serializer = ApkSerializer(data=self.apk_correct)
self.assertEqual(serializer.is_valid(), True)
UPD
It's also possible to use first method, but in this case you need to parse response data. And check if this data contains error with 'md5' key, something like:
def test_wrong_validate_md5_2(self):
url = reverse('apk-list')
response = self.client.post(url, {'md5':'test'}, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data,{'md5': Wrong md5')