I am writing a unit test for Django views.
class TestLog(unittest.TestCase):
"""Test for Contact"""
def setUp(self):
self.c = Client()
try:
self.bob = User.objects.create_user("mojo","b#example.com", "bmojo")
except :
print ''
def test_get_emails(self):
response = self.c.get('/text/')
self.assertEqual(response.status_code, 200)
def test_htmlemils(self):
response = self.c.get('/emails/html/upload')
self.assertEqual(response.status_code, 200)
The c = Client() takes the 'http://testserver' as domain which i want to overwrite ,i want to add my real domain in that test client ,is their way to customize the test Client ?
Django's Client extends RequestFactory so you should be able to pass in extra params as keyword arguments.
Try:
response = self.c.get('/emails/html/upload', SERVER_NAME="mydomain.com")
The code can help not only in unit test, but it can also help for DRF to use context in a serializer
ResponseSerializer(instance=obj, context={'request': get_request}).data
from django.test.client import RequestFactory
rf = RequestFactory()
rf.defaults['SERVER_NAME'] = 'my-site.com'
get_request = rf.get('/hello/')
Related
I have a function:
def test_function(request):
return request.device.id
which connected to endpoint /test_end/
I need to write a unittest but not working -> request.device is None
Test looks like this:
from django.test import TestCase
from django.test.client import Client
class Device:
def __int__(self, _id):
self.id = _id
class MyTest(TestCase):
def test_device(self):
client = Client()
response = client.get("/test_end", device=Device(42))
How to fix it?
I need to pass device to function into request
Try using RequestFactory to generate a request and then you can call the view directly like test_function(request)
For example :
from django.test import RequestFactory
request = self.factory.get('/test_end')
request.device = device() # here replace with the Device object
response = test_function(request)
print(response)
I was wondering how I can create unit tests for a view like this. I have several of these in my project.
def json_list_user(request):
data = list(Us.objects.values())
return JsonResponse(data, safe=False)
You can use self.client.get() and check what data the response holds, starting with status_code.
class MyTestCase(TestCase):
def test_json_list_user_response_status_code(self):
response = self.client.get(reverse_lazy("namespace_of_your_view"))
self.assertEqual(response.status_code, 200)
You can also check if data sent is as expected:
def setUp(self):
# create an object or few objects here
Us.objects.create()
def test_json_list_user_correct_list(self):
response = self.client.get(reverse_lazy("namespace_of_your_view"))
# check if response.json() have proper object/-s
I have this code here that creates entries in the table because they're required in order to create a new post. You need a user and a goal and goal category. I heard setUp() runs before every test so that's an issue as it could try to great instances that already exists. I'd like to run setUp() once before all the tests are run. How do I do this?
class PostTest(TestCase):
def setUp(self) -> None:
GoalCategory.objects.create(category='other', emoji_url='url')
self.user = UserFactory()
self.goal = GoalFactory()
def test_create_post(self):
response = self.client.post(reverse('post'),
data=json.dumps({'creator_id': str(self.user.uuid),
'goal_id': str(self.goal.uuid),
'body': 'Some text #Test'}),
content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
def test_no_goal_create_post(self):
response = self.client.post(reverse('post'),
data=json.dumps({'creator_id': str(self.user.uuid),
'goal_id': 'some messed up UUID',
'body': 'Some text #Test'}),
content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_no_user_create_post(self):
response = self.client.post(reverse('post'),
data=json.dumps({'creator_id': 'messed up user',
'goal_id': str(self.goal.uuid),
'body': 'Some text #Test'}),
content_type='application/json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
Actually for creating Django models the right answer is setupTestData() method. That method runs in database transaction which rolls back before the tests ends. Therefore all testing data are cleaned once the tests are done.
The code is:
#classmethod
def setUpTestData(cls):
GoalCategory.objects.create(category='other', emoji_url='url')
cls.user = UserFactory()
cls.goal = GoalFactory()
You can use setUpClass (classmethod) and override it in you TestCase class
"Hook method for setting up class fixture before running tests in the class."
class PostTest(TestCase):
#classmethod
def setUpClass(cls):
GoalCategory.objects.create(category='other', emoji_url='url')
cls.user = UserFactory()
cls.goal = GoalFactory()
Problem:
So my problem is I have a Flask microservice want to implement the unit tests to it so when I start writing my test cases I found that I need to authenticate the unit test client because of some endpoints need authorization and here comes the problem the whole authentication system in another service this service all can do about the authentication is to validate the JWT token and get user ID from it so here is one of the views.py
from flask_restful import Resource
from common.decorators import authorize
class PointsView(Resource):
decorators = [authorize]
def get(self, user):
result = {"points": user.active_points}
return result
and authorize decorator from decorators.py
import flask
import jwt
from jwt.exceptions import DecodeError, InvalidSignatureError
from functools import wraps
from flask import request
from flask import current_app as app
from app import db
from common.models import User
from common.utils import generate_error_response
def authorize(f):
"""This decorator for validate the logged in user """
#wraps(f)
def decorated_function(*args, **kwargs):
if 'Authorization' not in request.headers:
return "Unable to log in with provided credentials.", 403
raw_token = request.headers.get('Authorization')
if raw_token[0:3] != 'JWT':
return generate_error_response("Unable to log in with provided credentials.", 403)
token = str.replace(str(raw_token), 'JWT ', '')
try:
data = jwt_decode_handler(token)
except (DecodeError, InvalidSignatureError):
return generate_error_response("Unable to log in with provided credentials.", 403)
user = User.query.filter_by(id=int(data['user_id'])).first()
return f(user, *args, **kwargs)
return decorated_function
and the test case from tests.py
import unittest
from app import create_app, db
from common.models import User
class TestMixin(object):
"""
Methods to help all or most Test Cases
"""
def __init__(self):
self.user = None
""" User Fixture for testing """
def user_test_setup(self):
self.user = User(
username="user1",
active_points=0
)
db.session.add(self.user)
db.session.commit()
def user_test_teardown(self):
db.session.query(User).delete()
db.session.commit()
class PointsTestCase(unittest.TestCase, TestMixin):
"""This class represents the points test case"""
def setUp(self):
"""Define test variables and initialize app."""
self.app = create_app("testing")
self.client = self.app.test_client
with self.app.app_context():
self.user_test_setup()
def test_get_points(self):
"""Test API can create a points (GET request)"""
res = self.client().get('/user/points/')
self.assertEqual(res.status_code, 200)
self.assertEquals(res.data, {"active_points": 0})
def tearDown(self):
with self.app.app_context():
self.user_test_teardown()
# Make the tests conveniently executable
if __name__ == "__main__":
unittest.main()
My authentication system work as the following:
Any service (include this one) request User service to get user JWT
token
Any service take the JWT token decoded and get the user ID
from it
Get the user object from the database by his ID
so I didn't know how to make the authentication flow in the test cases.
Here is just an example. I skipped some little things such as create_app, jwt.decode(token) etc. I'm sure you can understand the main approach. Structure:
src
├── __init__.py # empty
├── app.py
└── auth_example.py
app.py:
from flask import Flask
from src.auth_example import current_identity, authorize
app = Flask(__name__)
#app.route('/')
#authorize()
def main():
"""
You can use flask_restful - doesn't matter
Do here all what you need:
user = User.query.filter_by(id=int(current_identity['user_id'])).first()
etc..
just demo - return current user_id
"""
return current_identity['user_id']
auth_example.py:
from flask import request, _request_ctx_stack
from functools import wraps
from werkzeug.local import LocalProxy
current_identity = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_identity', None))
def jwt_decode_handler(token):
"""
just do here all what you need. Should return current user data
:param str token:
:return: dict
"""
# return jwt.decode(token), but now - just demo
raise Exception('just demo')
def authorize():
def _authorize(f):
#wraps(f)
def __authorize(*args, **kwargs):
if 'Authorization' not in request.headers:
return "Unable to log in with provided credentials.", 403
raw_token = request.headers.get('Authorization')
if raw_token[0:3] != 'JWT':
return "Unable to log in with provided credentials.", 403
token = str.replace(str(raw_token), 'JWT ', '')
try:
# I don't know do you use Flask-JWT or not
# this is doesn't matter - all what you need is just to mock jwt_decode_handler result
_request_ctx_stack.top.current_identity = jwt_decode_handler(token)
except Exception:
return "Unable to log in with provided credentials.", 403
return f(*args, **kwargs)
return __authorize
return _authorize
Our test:
import unittest
from mock import patch
from src.app import app
app.app_context().push()
class TestExample(unittest.TestCase):
def test_main_403(self):
# just a demo that #authorize works fine
result = app.test_client().get('/')
self.assertEqual(result.status_code, 403)
def test_main_ok(self):
expected = '1'
# we say that jwt_decode_handler will return {'user_id': '1'}
patcher = patch('src.auth_example.jwt_decode_handler', return_value={'user_id': expected})
patcher.start()
result = app.test_client().get(
'/',
# send a header to skip errors in the __authorize
headers={
'Authorization': 'JWT=blabla',
},
)
# as you can see current_identity['user_id'] is '1' (so, it was mocked in view)
self.assertEqual(result.data, expected)
patcher.stop()
So, in your case you need just mock jwt_decode_handler. Also I recommend do not add any additional arguments inside a decorators. It will be hard to debugging when you have more than two decorators with a different arguments, recursion, hard processing etc.
Hope this helps.
Could you create some mock tokens in your unit testing framework (that your decorator can actually decode like in a real request) and send them in with your test client? An example of how that might look can be seen here: https://github.com/vimalloc/flask-jwt-extended/blob/master/tests/test_view_decorators.py#L321
I'm using Falcon, I need pass variable from middleware to resource, how can I do that?
main.py
app = falcon.API(middleware=[
AuthMiddleware()
])
app.add_route('/', Resource())
and auth
class AuthMiddleware(object):
def process_request(self, req, resp):
self.vvv = True
and resource
class Resource(object):
def __init__(self):
self.vvv = False
def on_get(self, req, resp):
logging.info(self.vvv) #vvv is always False
why self.vvv is always false? I have changed it in middleware to true.
First of all you are confussing what self means. Self affects only to the instance of the class, is a way of adding attributes to your class, therefore your self.vvv in AuthMiddleware is a complete different attribute from your self.vvv in your class Resource.
Second, you do not need to know anything from AuthMiddleware in your resource, thats why you want to use middleware. Middleware is a way to execute logic after or before each request. You need to implement your middleware so it raises Falcon exceptions or either modifies your request or response.
For example, if you don't authorize a request, you must raise an exception like this:
class AuthMiddleware(object):
def process_request(self, req, resp):
token = req.get_header('Authorization')
challenges = ['Token type="Fernet"']
if token is None:
description = ('Please provide an auth token '
'as part of the request.')
raise falcon.HTTPUnauthorized('Auth token required',
description,
challenges,
href='http://docs.example.com/auth')
if not self._token_is_valid(token):
description = ('The provided auth token is not valid. '
'Please request a new token and try again.')
raise falcon.HTTPUnauthorized('Authentication required',
description,
challenges,
href='http://docs.example.com/auth')
def _token_is_valid(self, token):
return True # Suuuuuure it's valid...
Check Falcon page examples.
From https://falcon.readthedocs.io/en/stable/api/middleware.html:
In order to pass data from a middleware function to a resource function use the req.context and resp.context objects. These context objects are intended to hold request and response data specific to your app as it passes through the framework.
class AuthMiddleware(object):
def process_request(self, req, resp):
# self.vvv = True # -
req.context.vvv = True # +
class Resource(object):
# def __init__(self): # -
# self.vvv = False # -
def on_get(self, req, resp):
# logging.info(self.vvv) # -
logging.info(req.context.vvv) # +
You should not use the attributes on middleware and resource instances for your request data. Since you only instantiate them once, modifying their attributes is generally not thread-safe.