DRF APIClient Delete data arrives in request.data, not request.query_params - django

I use DRF's APIClient to write automated tests. And while is was writing the first delete test, I found it very strange that the data passed through arrived in request.data, while if I use Axios or Postman, it always arrives in request.query_params.
Any explanation as to why this is, and preferably a method to use APIClient.Delete while the data arrives in query_params would be great!
My test:
import pytest
from rest_framework.test import APIClient
#pytest.fixture()
def client():
client = APIClient()
client.force_authenticate()
yield client
class TestDelete:
def test_delete(client):
response = client.delete('/comment', data={'team': 0, 'id': 54})
And my views
from rest_framework.views import APIView
class Comments(APIView):
def delete(self, request):
print(request.query_params, request.data)
>>> <QueryDict: {}> <QueryDict: {'team': ['0'], 'id': ['54']}>
Looked into DRF's APIClient. Feeding towards params doesn't seem to help. The delete method doesn't seem to have direct arguments that could help as well. So I'm a bit stuck.

Though some good options have been proposed, they didn't work with DRF's APIView. I ended up using urllib and encode it manually:
import pytest
from urllib.parse import urlencode
from rest_framework.test import APIClient
#pytest.fixture()
def client():
client = APIClient()
client.force_authenticate()
yield client
class TestDelete:
def test_delete(client):
response = client.delete(f'/comment{urlencode(data)}')

Related

Access basket from view called with unit test in Django Oscar

How do I attach a Django Oscar basket that has been created in a unit test to the request object?
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework.response import Response
from rest_framework.views import APIView
class BasketAPIAddView(LoginRequiredMixin, APIView):
"""
Update basket via REST API.
"""
def delete(self, request, format=None):
#
# cannot access `request.basket` here
#
return Response({})
# tests.py
from django.contrib.auth import get_user_model
from django.urls import reverse
from oscar.test.factories import create_basket
from rest_framework.test import APITestCase
User = get_user_model()
class BasketAPITests(APITestCase):
"""
Basket view test cases.
"""
def test_remove_basket_line(self):
basket = create_basket()
basket.owner = User.objects.create_user('user', password='pass')
basket.save()
self.client.login(username='user', password='password')
self.client.delete(reverse('delete-basket'))
You can do something like:
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APIRequestFactory
from oscar.test.factories import create_basket
User = get_user_model()
class BasketAPITests(APITestCase):
def test_remove_basket_line(self):
"""Check the method BasketAPIAddView.delete works as should."""
# Create the request, note the .delete which is the operation (BasketAPIAddView.delete)
request = APIRequestFactory().delete(reverse('<your url name>'))
# Create the basket
basket = create_basket()
basket.owner = User.objects.create_user('user', password='pass')
basket.save()
# Attach the basket to the request obj
request.basket = create_basket()
# Call the endpoint, with the proper request obj
response = BasketAPIAddView.as_view()(request)
# Some verifications
...
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
...
The key is to use a request factory and have the object at your will (adding what you need).
For more details you can take a look to https://www.django-rest-framework.org/api-guide/testing/
For normal requests oscar.apps.basket.middleware.BasketMiddleware adds the basket to the request, so it is possible you need to check the settings.MIDDLEWARE that your test project is using, or that the client provided by APITestCase is running middleware.
If you're developing an API you might also want to look at the way django-oscar-api handles session management and middleware.

Flask unittesting API requests

I am trying to write unit test cases for flas api server.
Can someeone please suggest ow to get rid of auth.login_required.
Tried mocking auth, but of no use.
with test_client its not hitting code block too.
api.py
from flask import Flask
from flask.ext.httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
#app.route('/')
#auth.login_required
def index():
print "In index"
response.status_code = 200
return response
Tried following http://flask.pocoo.org/docs/0.12/testing/
from src.api import app
from unittest import TestCase
class TestIntegrations(TestCase):
def setUp(self):
self.app = app.test_client()
def test_thing(self):
response = self.app.get('/')
Can someone please help ??
There are two ways to do so - first is to disable authorization in tests:
// in your test module
from api import app, auth
import unittest
#auth.verify_password
def verify_password(user, password):
"""Overwrite password check to always pass.
This works even if we send no auth data."""
return True
Another approach is to actually send the auth headers from tests (this way you can also test your authorization system):
from api import app
from base64 import b64encode
import unittest
class ApiClient:
"""Performs API requests."""
def __init__(self, app):
self.client = app.test_client()
def get(self, url, **kwargs):
"""Sends GET request and returns the response."""
return self.client.get(url, headers=self.request_headers(), **kwargs)
def request_headers(self):
"""Returns API request headers."""
auth = '{0}:{1}'.format('user', 'secret')
return {
'Accept': 'application/json',
'Authorization': 'Basic {encoded_login}'.format(
encoded_login=b64encode(auth.encode('utf-8')).decode('utf-8')
)
}
class TestIntegrations(unittest.TestCase):
def setUp(self):
self.app = ApiClient(app)
def test_thing(self):
response = self.app.get('/')
print(response.data)
The ApiClient helper can also define post, delete methods which will be similar to get.
The full source code with examples is here.

Django View Testing Returning 301 or not found

I'm trying to test the response code of a view, but I'm either getting a 301 or does not exist.
urls.py
...
url(r'^myview/(?P<view_id>.*)/$', view_myview.index, name='myview'),
...
Test code 1:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123')
self.assertEqual(response.status_code, 200)
The above code gives:
AssertionError: 301 != 200
Test code 2:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123/')
self.assertEqual(response.status_code, 200)
The above code gives:
Mymodel matching query does not exist.
All I want to do is simple testing of my views to ensure they aren't throwing an error code, but I can't seem to find the right way to do it and I've tried many, many suggestions from the internets. Is there a different way to pass in view_id? What if I also want to throw in some query parameters?
EDIT: Updating to show the workaround I've used to accomplish what I'm trying to do, as horrible as it may be. I found that using dumpdata and fixtures took FOREVER.
from django.test import TestCase
from django.test import Client
import os
from . import urls_to_test # just a simple list of strings
class SimpleTest(TestCase):
""" Simply test if views return status 200 """
def setUp(self):
self.client = Client()
print('Dumping production database...')
os.system("sudo mysqldump mydb > /tmp/mydb.sql")
print('Loading production data into test database...')
os.system("sudo mysql test_mydb < /tmp/mydb.sql")
os.system("sudo rm -rf /tmp/mydb.sql")
def test_details(self):
for u in urls_to_test.test_urls:
print('Testing {}'.format(u))
response = self.client.get(u)
self.assertEqual(response.status_code, 200)
print('{} URLs tested!'.format(len(urls_to_test.test_urls)))
The first one doesn't work because Django is redirecting to the version with a final slash.
The second one tells you exactly why it doesn't work: you haven't created an item with id 123 - or indeed any items at all - within the test.
Consider creating object before testing its existance:
import unittest
from django.test import Client
from app.models import YourModel
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.obj = YourModel.objects.create(*your object goes here*)
def test_details(self):
response = self.client.get('/myview/123/') # It may be not /123/. It depends on how you generate url for model
self.assertEqual(response.status_code, 200)

Django AttributeError: object has no attribute 'assertContains'

I'm trying Django test fixtures from the official documentation, but my test class can't find assertContains.
from django.utils import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.post('/register',
{'username': '123',
'password': '123',
follow=True)
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Logout")
self.assertNotContains(response, "Login")
assertContains is a Django specific feature, not Python specific. Thus, make sure the test class is subclassed from TestCase in django.test, not TestCase in (python) unittest.
i.e.
from django.test import TestCase
class SimpleTest(TestCase):
self.assertContains(response, "Logout")
That's it (the first answer), In my case, i'm using django test_plus, and thats was the issue:
from django.test import TestCase
class BlogTestCase(TestCase):
def test_blog(self):
response = self.get('blog_detail', self.blog.slug)
Change the import to
from test_plus.test import TestCase

Accessing the request.user object when testing Django

I'm trying to access the request.user object when testing my app using django's client class.
from django.test import TestCase
from django.test.client import Client
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.client.login( username="foo", password="bar")
def test_one(self):
response = self.client.get("/my_profile/")
self.fail( response.request.user )
This will obviously fail, but it fails because response.request doesn't have a user attribute.
AttributeError: 'dict' object has no attribute 'user'
Is there any way to access the user object from the client instance? I want to do this in order to check if some test user's data is properly rendered. I can try to tackle this by setting up all the necessary info during setUp, but before I do that I wanted to check if I'm not just missing something.
This may seem a roundabout way of doing it but it can be useful.
Use RequestFactory, its pretty straightforward. It imitates the request object.
from django.test import TestCase, RequestFactory
from django.test.client import Client
from django.contrib.auth.models import User
class SomeTestCase(TestCase):
def setUp(self):
self.client = Client()
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='foo', email='foo#bar',
password='bar')
def test_one(self):
self.client.login( username="foo", password="bar")
request = self.factory.get("/my_profile/")
request.user = self.user
#rest of your code
def tearDown(self):
self.user.delete()
I hope that was helpful.
Use response.context['user'].
User is automatically available in the template context if you use RequestContext. See auth data in templates doc.
Otherwise i believe you should just query it:
def test_one(self):
response = self.client.get("/my_profile/")
user = User.objects.get(username="foo")
self.fail( user.some_field == False )