Flask unittesting API requests - flask

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.

Related

How to integrate webargs + marshmallow?

I am only beginner in flask. Trying to integrate marshmallow and webargs. It perfectly works in flask-restful Resource class. But when I use a simple flask route it does not work
routes.py
class UserAPI(Resource):
#use_args(UserSchema())
def post(self, *args):
print(args)
return 'success', 201
def get(self):
return '<h1>Hello</h1>'
#bp.route('/test/', methods=['POST'])
#use_kwargs(UserSchema())
def test2(*args, **kwargs):
print(args)
print(kwargs)
return 'success', 201
api.add_resource(UserAPI, '/', endpoint='user')
I've added error handler which is necessary when using use_args
from webargs.flaskparser import parser, abort
from webargs import core
#parser.error_handler
def webargs_validation_handler(error, req, schema, *, error_status_code, error_headers):
status_code = error_status_code or core.DEFAULT_VALIDATION_STATUS
abort(
400,
exc=error,
messages=error.messages,
)
That's what I'm getting when I make request to Resource endpoint what is normal
And that's what I'm getting when I make request to a simple flask route what is not normal
I want to be able to use both ways
Found answer in webargs docs :)
https://webargs.readthedocs.io/en/latest/framework_support.html#error-handling
from flask import jsonify
# Return validation errors as JSON
#app.errorhandler(422)
#app.errorhandler(400)
def handle_error(err):
headers = err.data.get("headers", None)
messages = err.data.get("messages", ["Invalid request."])
if headers:
return jsonify({"errors": messages}), err.code, headers
else:
return jsonify({"errors": messages}), err.code

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.

Using requests module with Django APITestCase and APIClient?

I'm trying to test my command line client with Django. I want to use the requests module in my client to fetch data from Django, but I'm testing this inside an APITestCase class so I can create factories using Factory_Boy.
I get a connection refused error.
The file in my front-end to call the view:
from . import urls
import requests, json
HEADERS = {'content-type':'application/json'}
BASE_URL = 'http://127.0.0.1:80/'
def post(url, **data):
query = json.dumps(data)
r = requests.post(BASE_URL+url, data=query, headers=HEADERS)
return r.json()
def get(url, **data):
query = json.dumps(data)
r = requests.get(BASE_URL+url, data=query, headers=HEADERS)
return r.json()
The tests file for Django app, inside a APITestCase class:
def setUp(self):
self.client = APIClient()
self.populate_addresses()
self.populate_carriers()
self.populate_drivers()
def test_requests(self):
a = query.query_address(err_msg='Stop not found.',
conf_msg='Create new stop.')
def populate_addresses(self):
self.Address__A1 = factories.AddressFactory(
name='BANDINI',
city='CHATSWORTH',
zip_code='92392',
state='CA',
street='12345 BANDINI BLVD')
self.Address__A2 = factories.AddressFactory(
name='BALL METAL',
city='FAIRFIELD',
zip_code='92392',
state='CA',
street='2400 HUNTINGTON BLVD')
APITestCase has already APIClient module.
You can access it with self.client and you can send request like self.client.post(#url,#data) inside your test methods. You can get more detail about APIClient here

Django: how to mock a class inside the API view

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)

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)