Django Test Object created is empty - python

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))

Related

How to pass request field into Django test?

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)

Mocking a request object to pass to ViewSet create() method

I'm learning unittest and unittest.mock, and struggling with the concepts and implementations primarily with mock.
For context, what I'm playing with is a Django / DRF API and Redis. I'm trying to write tests which require mocking the Redis calls.
Here is the test I'm working on:
# tests.py
import unittest
from unittest.mock import patch
from core.views import KeysViewSet
class KeysViewSetTestCase(unittest.TestCase):
def setUp(self):
self.json_object = {'key': 'hello', 'value': 'world'}
self.view = KeysViewSet()
def test_create(self):
with patch('core.views.RedisUtil.create') as mocked_create:
mocked_create.return_value.data = True
created = self.view.create(self.json_object)
The views.py:
# viefws.py
# Third party imports
from rest_framework import status, viewsets
from rest_framework.response import Response
# Croner imports
from .serializers import KeysSerializer
# Import Redis
from .utils import RedisUtil
class KeysViewSet(viewsets.ViewSet):
"""
METHOD URI DESCRIPTION
GET /api/keys/<:key>/ Returns specific value from key
POST /api/keys/ Creates a new key/value
DELETE /api/keys/<:key>/ Deletes a specific key
"""
def __init__(self, *args, **kwargs):
"""
Instantiate the RedisUtil object.
"""
self.redis_util = RedisUtil()
def create(self, request):
"""
Creates a key/pair in the Redis store.
"""
print(request)
# Serialize the request body
serializer = KeysSerializer(data=request.data)
# If valid, create the key/value in Redis; if not send error message
if serializer.is_valid():
return Response(self.redis_util.create(serializer.data))
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
And the utils.py that handles the Redis actions:
# utils.py
# Django imports
from django.conf import settings
# Redis imports
import redis
class RedisUtil:
"""
Instantiates the Redis object and sets the connection params.
"""
def __init__(self):
self.redis_instance = redis.StrictRedis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT
)
def create(self, data):
"""
Creates the key/value pair from request body.
"""
return self.redis_instance.set(data['key'], data['value'])
The error I'm getting is the following:
Found 1 test(s).
System check identified no issues (0 silenced).
{'key': 'hello', 'value': 'world'}
E
======================================================================
ERROR: test_create (core.tests.KeysViewSetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/cjones/Projects/test/kv-store/api/src/core/tests.py", line 19, in test_create
created = self.view.create(self.json_object)
File "/Users/cjones/Projects/test/kv-store/api/src/core/views.py", line 32, in create
serializer = KeysSerializer(data=request.data)
AttributeError: 'dict' object has no attribute 'data'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
I know what I'm getting this error: the KeysViewSet().create() in views.py is expecting the object I'm passing to be at request.data = {} and it isn't.
Resolving it is what I'm trying to figure out.
I know there is the request library, but not sure I would need to import it just for this. DRF has several options including APIRequestFactory, but those will try to spin up a database and create an error as it will try to connect to Redis which it won't be able to.
How should I go about resolving this issue?
What I ended up doing to resolve the issue was the following:
# test.py
import unittest
from unittest.mock import patch
from core.views import KeysViewSet
class KeysViewSetTestCase(unittest.TestCase):
def setUp(self):
self.json_object = {'key': 'hello', 'value': 'world'}
self.view = KeysViewSet()
class Request:
def __init__(self, data):
self.data = data
self.request = Request(self.json_object)
def test_create(self):
with patch('core.views.RedisUtil.create') as mocked_create:
mocked_create.return_value.data = True
created = self.view.create(self.request)
That being said, I'm not sure that this is a desirable solution so I'm reluctant to accept it as the correct answer. Looking forward to feedback.

Mock/patch function in view

from flask import request
from flask.views import MethodView
from unittest.mock import patch
from router.views.time_view import (
_utcnow
)
class StateSetupView(MethodView):
#app.route("/state-setup", methods=['POST'])
def post():
print(f"Non-mocked version: {_utcnow()}")
with patch('router.views.time_view._utcnow', return_value = "LOL"):
print(f"Mocked version: {_utcnow()}")
I can't seem to mock the function return value in runtime. The code above returns the same value in both instances. I do not want to wrap this in a pytest, this needs to work in a view.
Just simple replacing the function straight up worked.
router.views.time_view._utcnow = lambda: 12346789

Django Testing - loading data into the database before loading the apps

I'm currently writting some tests for a Django app (+ REST framework), and having some issues loading the test data into the database.
Let me explain with some (very simplified) code :
I have a django view which is something like :
view.py
from myapp.models import Item
from myapp.utils import MyClass
# need to initialize with the set of items
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
class MyView(APIView):
def post(selfself, request):
result = my_class_object.process(request.data)
return Response(result)
So basically I need to initialize a class with some data from the database, and I then use this class in my view to process the data received by the endpoint.
Now the test :
my_test.py
from rest_framework.test import APILiveServerTestCase
from myapp.models import Item
class MyTest(APILiveServerTestCase):
def setUp(self):
self.URL = '/some_url_linking_to_myview/'
# load some data
Item.objects.create(name="first item")
Item.objects.create(name="second item")
def test_myview_return_correct_result(self):
post_data = {"foo"}
response = self.client.post(self.URL,
data=post_data,
format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, {"my_expected_result"})
When running the test, what currently happens is that view.py is loaded before the setUp() method get excecuted, so when I instantiate the class with these two lines :
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
the database is still empty.
I'm wondering if there is a way to either get the data into the database before view.py get executed, or maybe somehow force reloading the app after setUp(), or instantiate my class somewhere else so it gets called after loading the data ?
thanks !
I think you are looking for setUpTestData().
Here is roughly how I set this up if I am going to use a significant amount of data:
tests.py
from django.test import TestCase
from .tests.test_data import base_data
class MyClassTest(TestCase):
#classmethod
def setUpTestData(cls):
base_data.base_data(cls)
base_data.py
from .models import MyClass
def base_data(cls):
cls.MyClass1 = MyClass.objects.create(
name="first_name"
)
cls.MyClass2 = MyClass.objects.create(
name="second_name"
)
Of course, you can do everything directly in the setUpTestData() function, if you would rather have your test data sitting up top.
Why not put the initiate code in the function and call from inside the post.
class MyView(APIView):
def initialize(self):
item_set = {item.name for item in Item.objects.all()}
my_class_object = MyClass(item_set)
def post(self, request):
self.initialize()
result = my_class_object.process(request.data)
return Response(result)
Edit 1
Optionally, you can use fixture to load MyClass objects in the database beforehand
class MyTest(APILiveServerTestCase):
fixtures = [
// my class objects fixtures file
]
def setUp():
// rest of the code

How can I mock a method of a Django model manager?

Here is a little class (in myapp/getters.py):
from django.contrib.auth.models import User
class UserGetter:
def get_user(self):
return User.objects.get(username='username')
I would like to mock out the call to User.objects.get, return a MagicMock, and test that the method returns what I injected. In myapp/tests/tests_getters.py:
from unittest import TestCase
from django.contrib.auth.models import User, UserManager
from mock import patch, create_autospec
from myapp.getters import UserGetter
class MockTestCase(TestCase):
#patch('myapp.getters.User', autospec=True)
def test(self, user_class):
user = create_autospec(User)
objects = create_autospec(UserManager)
objects.get.return_value = user
user_class.objects.return_value = objects
self.assertEquals(user, UserGetter().get_user())
But when I run this test (with python manage.py test myapp.tests.tests_getters) I get
AssertionError:
<MagicMock name='User.objects().get()' spec='User' id='4354507472'> !=
<MagicMock name='User.objects.get()' id='4360679248'>
Why do I not get back the mock I injected? How can I write this test correctly?
I think this is your problem:
user_class.objects.return_value = objects
You instruct the mock to have a function "objects" that returns the objects on the right side.
But your code never calls any objects() function. It accesses the User.objects property, User is a Mock here, so User returns a new Mock on property access.

Categories