Django: how to mock a class inside the API view - django

Title might be a little confusing.
Say I have an APIView with a post method. Inside the post method, I introduced a class that has its own method. In this case, it's a class that deals with uploading to S3, which is something I want to skip when running unittest.
class SomeView(APIView):
def post(self):
# do something here
input1 = some_process(payload_arg1)
input2 = some_other_process(payload_arg2)
uploader = S3Uploader()
s3_response = uploader.upload_with_aux_fxn(input1, input2)
if s3_response['status_code'] == 200:
# do something else
return Response('Good job I did it!', status_code=200)
else:
return Response('noooo you're horrible!', status_code=400)
Real code has different function calls and responses, obviously.
Now I need to mock that uploader and uploader.upload_with_aux_fxn so I don't actually call S3. How do I mock it?
I tried in my test script
from some_place import S3Uploader
class SomeViewTestCase(TestCase):
def setUp(self):
self.client = APIClient()
uploader_mock = S3Uploader()
uploader_mock.upload_support_doc = MagicMock(return_value={'status_code': 200, 'message': 'asdasdad'}
response = self.client.post(url, payload, format='multipart')
But I still triggered S3 upload (as file shows up in S3). How do I correctly mock this?
EDIT1:
My attempt to patch
def setUp(self):
self.factory = APIRequestFactory()
self.view = ViewToTest.as_view()
self.url = reverse('some_url')
#patch('some_place.S3Uploader', FakeUploader)
def test_uplaod(self):
payload = {'some': 'data', 'other': 'stuff'}
request = self.factory.post(self.url, payload, format='json')
force_authenticate(request, user=self.user)
response = self.view(request)
where the FakeUplaoder is
class FakeUplaoder(object):
def __init__(self):
pass
def upload_something(self, data, arg1, arg2, arg3):
return {'status_code': 200, 'message': 'unit test', 's3_path':
'unit/test/path.pdf'}
def downlaod_something(self, s3_path):
return {'status_code': 200, 'message': '', 'body': 'some base64
stuff'}
unfortunately this is not successful. I still hit the actual class
EDIT 2:
I'm using Django 1.11 and Python 2.7, in case people need this info

I guess the correct approach to it would be save the file within a model with FileField, and then connect Boto to handle upload in production scenario.
Take a good look at:
https://docs.djangoproject.com/en/2.2/ref/models/fields/#filefield
and
https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#model
this approach would preserve Django default behavior, making things more testable with Django's default test client.

Take a look at vcrpy. It records request to external API once and then replays answer every time you run your tests. No need to manually mock anything.

Here's an example of how I would mock that S3Uploader in an APITestCase.
from rest_framework import status
from unittest import mock
from unittest.mock import MagicMock
class SomeViewTestCase(APITestCase):
#mock.patch("path.to.view_file.S3Uploader")
def test_upload(self, s3_uploader_mock):
"""Test with mocked S3Uploader"""
concrete_uploader_mock = MagicMock(**{
"upload_with_aux_fxn__return_value": {"status_code": 200}
})
s3_uploader_mock.return_value = concrete_uploader_mock
response = self.client.post(url, payload, format='multipart')
self.assertEqual(response.status_code, status.HTTP_200_OK)
s3_uploader_mock.assert_called_once()
concrete_uploader_mock.upload_with_aux_fx.assert_called_once()

Try using MagicMock like below
from unittest import mock
from storages.backends.s3boto3 import S3Boto3Storage
class SomeTestCase(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.view = ViewToTest.as_view()
self.url = reverse('some_url')
#mock.patch.object(S3Boto3Storage, '_save', MagicMock(return_value='/tmp/somefile.png'))
def test_uplaod(self):
payload = {'some': 'data', 'other': 'stuff'}
request = self.factory.post(self.url, payload, format='json')
force_authenticate(request, user=self.user)
response = self.view(request)

Related

how to test a unit test using pytest django if there is a function defined but not the class?

#api_view(['GET'])
def get_search(request):
search_text = get_searchText()
return Reponse({"search-txt":search_text})
here, search_txt is an api for searching the text,and search_text is a variable
I tried below but was unable to test the above code,
from django.test import TestCase, Client
class TestViews(TestCase):
def setUp(self):
self.client = Client()
self.searchtxt_url = reverse('search-txt')
def test_search_project(self):
response.self.client.get(self.searchtxt_url)
self.assertEquals(response.status_code, 200)
what can be a possible way to test a function with api
I am guessing what you wanted to write was
def test_search_project(self):
response = self.client.get(self.searchtxt_url)
self.assertEquals(response.status_code, 200)
You have to store the response of your self.searchtxt_url in the response object and then you can assert its fields/values.

How to test mp3 upload in Django DRF, via PUT?

I try to test mp3 modification (hence PUT). I have the following so far:
client = Client()
with open('my_modified_audio.mp3', 'rb') as fp:
response = client.put(
f"/resource/{resource_id}/",
data={'audio': fp})
However, I get response.status_code == 415 because the serializer line in DRF's ModelViewSet
serializer = self.get_serializer(instance, data=request.data, partial=partial).
fails with
rest_framework.exceptions.UnsupportedMediaType: Unsupported media type "application/octet-stream" in request.
I have tried setting format="multipart", setting the content type to json or form-encoded, nothing helped so far. The Resource model uses FileField:
class Resource(models.Model):
audio = models.FileField(upload_to='uploads')
How can I make this put request work?
I think that the following will work:
The client:
import requests
...
client = Client()
files = [('audio': open('my_modified_audio.mp3', 'rb'))]
url = f"/resource/{resource_id}/"
# response = client.put(url, data=None, files=files)
# You can test it using the `requests` instead of Client()
response = requests.put(url, data=None, files=files)
The serializer:
class AudioSerializer(serializers.Serializer):
""" AudioSerializer """
audio = serializers.FileField(...)
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
The view:
from rest_framework.generics import UpdateAPIView
class AudioView(UpdateAPIView):
...
parser_classes = (FormParser, MultiPartParser)
serializer_class = AudioSerializer
...
Inspired by #athansp's answer, I compared the source code of client.post and client.put and it turns out, put's implementation slightly differs from post, so a workable way of submitting files with put is:
from django.test.client import MULTIPART_CONTENT, encode_multipart, BOUNDARY
client = Client()
with open('my_modified_audio.mp3', 'rb') as fp:
response = client.put(
f"/resource/{resource_id}/",
data=encode_multipart(BOUNDARY, {
'other_field': 'some other data',
'audio': fp,
}),
content_type=MULTIPART_CONTENT
)
Lol.

Django PUT TestCase fails if client initialised in setUpTestData() but passes if client initialized in setUp()

I am writing tests where every test case passes except the PUT
from django.test import TestCase
from rest_framework.test import APIClient
class ViewTestCase(TestCase):
#classmethod
def setUpTestData(cls):
cls.client = APIClient()
def setUp(self):
"""setUp() runs before every single test method."""
self.user_data = {'first_name': "John", 'last_name': "Doe", 'email_id': "john#doe.com",
'phone_number': "987654321", 'is_verified': False}
self.response = self.client.post(
reverse('create'),
self.user_data,
format='json')
def test_api_can_update_user(self):
user = User.objects.get()
changes = {'first_name': "Johnny"}
changed_user_data = {**self.user_data, **changes}
response = self.client.put(
reverse('details', kwargs={'email': user.email_id}),
changed_user_data,
format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
This test case fails with response.status_code = 415 (unsupported media type)
whereas if I just move the client initialization from setUpTestData() to setUp()
everything passes.
def setUp(self):
self.client = APIClient() # Test case passed now.
...
There are other tests for GET, POST, DELETE all of which pass
irrespective of whether client instance is shared(setUpTestData) or not.
PS: All apis including PUT work from DRF web api view.
From my understanding and with your test above client is not an instance attribute of the class instead it is a class attribute so hence the error response. Try changing self.client to cls.client in your setUp method and test_api_can_update_user method.
Also in my experience it is advisable to initialise client before creating the testcases.
using APITestCase instead of TestCase helpled.
APITestCase initializes self.client = APIClient() for you
In this case you dont need to initialize cls.client.
class ViewTestCase(APITestCase):
def setUp(self):
"""setUp() runs before every single test method."""
....

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)

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.