Url not parsed when testing a ModelViewSet - django

I am trying to test a Django Rest Framework view. When I call my endpoint from a real api client, pk is correctly set. When it is called from the test, pk is None.
class ResourceViewSet(ModelViewSet):
serializer_class = ResourceSerializer
#action(detail=True)
def foo(self, request, pk=None):
print(pk) # None when called by the test
def test_foo(client: Client, db):
request = factory.post(f'/api/resource/1/foo/')
view = ResourceViewSet.as_view({'post': 'foo'})
response = view(request)
How should I fix my test?

When testing the view directly as you are doing, you are bypassing the url resolving/mapping logic. Therefore, you should pass the parameters as args/kwargs, in the end you are calling the foo function:
def test_foo(client: Client, db):
request = factory.post(f'/api/resource/1/foo/')
view = ResourceViewSet.as_view({'post': 'foo'})
response = view(request, pk=1)
If you'd like to test the whole stack, also the urls, I'd recommend you use the APIClient.

Related

Testing a custom auth backend with Django RestFramework

I created a custom authentication backend for my DRF application.
I can't figure out how to test it.
Calling the client.post calls my authenticate function (cause that's in my view)
But I need to mock an internal method in my ModelBackend.
Kinda confused how to go about this?
View:
class Web3UserToken(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, **kwargs):
public_address = request.data["public_address"]
web3 = Web3Backend()
user, token = web3.authenticate(request)
if token:
return JsonResponse({'token': token})
else:
return Response({'message': 'Missing token'}, status=400)
Test:
class TestWeb3AuthBackend(APITestCase):
def setUp(self):
#TODO: set up test user
self.client = APIClient()
#self.factory = APIRequestFactory()
def test_authenticatepasseswithexistinguser(self):
self.user = Web3User(public_address=TEST_PUBLIC_ADDRESS)
auth_backend = Web3Backend()
import ipdb; ipdb.sset_trace()
request = self.client.post('/api/token/', {'public_address': TEST_PUBLIC_ADDRESS, 'nonce': '0xsomething_random'},follow=True)
with mock.patch.object(auth_backend, '_check_nonce', return_value=True) as method:
token, user = auth_backend.authenticate(request)
self.assertTrue(token)
self.assertTrue(user)
I suggest using RequestFactory for creating a request and passing it to authenticate method, instead of sending a request via Django's test client. This is a unit test and its aim is to test authenticate method of Web3Backend. You don't need to test this functionality through an api call.

how to test django views that use requests module get method?

I know how to write django test code.
But I don't know how to test with views that inlcude requests.get code
View code is like this.
class CertificateAPIView(APIView):
permission_classes = [IsAuthenticated, IsOwner]
def put(self, request):
imp = request.data.get("imp")
response = requests.get('another server')
uid = response.get('uid')
if uid == None:
return Response(
{"detail": ValidationError(_("Certification is failed"))},
status=status.HTTP_400_BAD_REQUEST,
)
... more condition branch logics.
return ...
imp value is automatically created by 'another server' that I am not managing.
This code is related with another server response and check condition branch with response.
But before getting response with imp vlue, we can't check whether response is error.

How to get a testing http request in django testcase?

I'm working in a solution with only an GraphQL API, so all my logic are in forms. The save method of one of my forms receives the http request. I use the request to get some data for mailing. So, I'm trying to make a test case of this form but I don't know how to pass the request object.
class SignUpForm(forms.ModelForm):
...
def save(self, request, *args, **kwargs):
...
How can I pass the request object to form in a test case?
You can instantiate HttpRequest and use it as a regular request in your test:
fake_request = HttpRequest()
fake_request.user = AnonymousUser()
fake_request.META['SERVER_NAME'] = site.domain
fake_request.META['SERVER_PORT'] = 80
s = SessionStore()
s.create()
fake_request.session = s
In your case you might need to fill more fields

django testing class based view

I have a Class based view defined as:
class Myview(LoginRequiredMixin, View):
def post():
#.......
to test this view i tried this
class MyViewTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='soos#i.com', password='vvggtt')
def view_test(self):
# Create an instance of a POST request.
request = self.factory.post('/my-url/')
request.user = self.user
response = MyView(request)
print (response,"**")
self.assertEqual(response.status_code, 200)
But this gives this error.
response = MyView(request)
TypeError: __init__() takes 1 positional argument but 2 were given
I understand why this error is coming (cinstructor of MyView has 2 ars) but how do i remove it? i couldnt get the details on searching.
we can use django test client
from django.test import Client
class MyViewTest(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user(
username='jacob', email='soos#i.com', password='vvggtt')
def view_test(self):
# Create an instance of a POST request.
self.client.login(username="jacob", password="vvggtt")
data = {'name': 'test name'}
res = self.client.post('/my-url/', data)
print(res)
self.assertEqual(res.status_code, 200)
From the docs:
# Use this syntax for class-based views.
response = MyView.as_view()(request)
Try
response = MyView(request=request)
There's a section of the Django docs called Testing Class Based Views which addresses this:
In order to test class-based views outside of the request/response cycle you must ensure that they are configured correctly, by calling setup() after instantiation.
So in your case this looks something like:
def view_test(self):
# Create an instance of a POST request.
request = self.factory.post('/my-url/')
request.user = self.user
my_view = MyView()
my_view.setup(request)
response = my_view.post(request)
self.assertEqual(response.status_code, 200)

request.POST returns old values after updating it in custom middleware - django 1.11.9

I am using django 1.11.9
I want to add client_id and client_secret to the django POST request.
Here is how my middleware.py file looks like:
class LoginMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# auth_header = get_authorization_header(request)
# Code to be executed for each request before
# the view (and later middleware) are called.
#Add Django authentication app client data to the request
request.POST = request.POST.copy()
request.POST['client_id'] = '12345678'
request.POST['client_secret'] = '12345678'
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
Middleware is being successfully processed when I check it with a debugger. Thought when a view is called the 'client_id' and 'client_secret' fields are missing in the request.
After some experimenting i figure out that request is not getting updated and when it is called in a different view, it returns old values.
I am later using request in rest_framework_social_oauth2. And this is the point when 'client_id' and 'client_secret' disappear.
class ConvertTokenView(CsrfExemptMixin, OAuthLibMixin, APIView):
"""
Implements an endpoint to convert a provider token to an access token
The endpoint is used in the following flows:
* Authorization code
* Client credentials
"""
server_class = SocialTokenServer
validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
oauthlib_backend_class = KeepRequestCore
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
import pdb ; pdb.set_trace()
# Use the rest framework `.data` to fake the post body of the django request.
request._request.POST = request._request.POST.copy()
for key, value in request.data.items():
request._request.POST[key] = value
url, headers, body, status = self.create_token_response(request._request)
response = Response(data=json.loads(body), status=status)
for k, v in headers.items():
response[k] = v
return response
I need to add client_id and client_secret to the request body, so it can be later used by rest_framework_social_oauth2.
What could be the problem? How to properly update the request?
As you're working with request and processing a request, you have to implement process_request method, so the result will be something like:
class LoginMiddleware(object):
def process_request(self, request):
request.session['client_id'] = '12345678'
and then in your view:
def your_view(request):
client_id = request.session['client_id']