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)
Related
I am trying to follow instructions from the Django documentation:
https://docs.djangoproject.com/en/3.2/topics/testing/overview/
However when I try to create the object for the test. The object is empty??
This is my code:
from django.test import TestCase, Client
from django.urls import reverse
from .models import ShortURL
from .views import generate_shortURL, redirect, home
from datetime import datetime
url = "https://stackoverflow.com/"
time = datetime.now()
class TestURLShortener(TestCase):
def setup(self):
self.client = Client()
obj= ShortURL.objects.create(original_url=url, short_url='zqSkSQ', time_date_created=time, count=2)
obj.save()
def test_creating_short_URL_POST(self):
"""
Test to create short Urls
"""
short_url_from_db = ShortURL.objects.all()
print(f'short_url_from_db : {short_url_from_db}')
response = self.client.post(reverse('generate_shortURL'), data={'original_url': url})
generated_short_url = response.context["chars"]
self.assertEquals(generated_short_url, 'afasdf')
This is the results when I run the test:
short_url_from_db prints out this <QuerySet []> instead of the object I wanted it to print out from the setup function.
How can I get the object I created to use in this test?
You need to use setUp and not setup as the function for setting up your test case.
You also don't need to call save() if you use create().
An alternative you could make use of is setUpTestData()
This technique allows for faster tests as compared to using setUp().
class TestURLShortener(TestCase):
#classmethod
def setUpTestData(cls):
# Set up data for the whole TestCase
cls.obj = ShortURL.objects.create(original_url=url, short_url='zqSkSQ', time_date_created=time, count=2)
...
def setUp(self):
self.client = Client()
def test_obj_type(self):
self.assertTrue(isinstance(self.obj, ShortURL))
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 trying to test my Django app which has a proxy API which is instantiated in its own module.
api.py
class ProxyApi(object):
def __init__(self, server_info):
pass
def validate_login(self, credentials):
# call to real api here
api = ProxyAPi()
middlewares.py
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
# do something with proxy api
views.py
from mymodule.api import api
class TaskView(LoginRequiredMixin, FormView):
def get(self, request):
if api.validate_login():
# do something with proxy api
tests.py
class InputTasksViewTest(TestCase):
#mock.patch('mymodule.api.ProxyAPi')
def test_add(self, mock_api):
mock_api.validate_login.return_value = True
response = self.client.get(reverse('task'))
The original validate_loginis still called.
I would like to know how to handle the instantiation of ProxyApi while still retaining mocking capacity.
Ok I found my own solution, the problem was that once Django started, it read some files (like models or views or middlewares) that automatically instantiated api variable from import.
I just needed to defer this instantiation so I can mock the ProxyApi object, here's what I did:
api = SimpleLazyObject(lambda: ProxApi())
You have def validate_login(self, credentials): in api
and in middleware you define below code. So How you will send creadentials to API from middleware api.validate_login(<You should send credentials to api as parameter>):
from mymodule.api import api
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
if api.validate_login():
pass
I'm trying to mock the "self.api.friends.get" method in VKAuth class:
import vk
class VKAuth(object):
def __init__(self, access_token, user):
self.session = vk.Session(access_token = access_token)
self.api = vk.API(self.session)
def follow(self):
vk_friends = self.api.friends.get()
from the test module test_views.py:
from mock import patch
from ..auth_backends.vk_backend import VKAuth
class AddUsersToList(TestCase):
#patch.object(VKAuth.api.friends, 'get')
def test_auth_vk(self, mock_get):
... etc ...
And I get an error during testing:
AttributeError: <class 'accounts.auth_backends.vk_backend.VKAuth' doens't have the attribute 'api'
What am I doing wrong? How to get an access to this method in this class structure?
You're trying to mock a class itself, not it's instance. And the class doesn't have the api attribute, as it's created in your __init__(). Change your code to:
def test_auth_vk(self, mock_get):
vk_auth = VKAuth(access_token, user)
with mock.patch('vk_auth.api.friends') as friends_mock:
friends_mock.get.return_value = None
# Invoke the code that calls your api, passing the "vk_auth" variable as a backend.
# ...
friends_mock.mock.get.assert_called_with(your_arguments)
If you can't just pass an auth backend to your code, look up the place where it is instantiated and mock that place.
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/')