Django test client, not creating models (--keepdb option being used) - django

I'm trying to setup some models in the test database and then post to a custom form which includes a file upload.
Nothing seems to be persisting in the database, and I'm unsure about why the test when I perform the POST is sending back a 200 response? With follow=False, shouldn't it be a 302?
Also, when I try to look for the model in the database, it finds nothing.
And when I look at the database when using the --keepdb option, nothing is there either?
What am I doing wrong?
class ImportTestCase(TestCase):
remote_data_url = "http://test_data.csv"
local_data_path = "test_data.csv"
c = Client()
password = "password"
def setUp(self):
utils.download_file_if_not_exists(self.remote_data_url, self.local_data_path)
self.my_admin = User.objects.create_superuser('jonny', 'jonny#testclient.com', self.password)
self.c.login(username=self.my_admin.username, password=self.password)
def test_create_organisation(self):
self.o = Organization.objects.create(**{'name': 'TEST ORG'})
def test_create_station(self):
self.s = Station.objects.create(**{'name': 'Player', 'organization_id': 1})
def test_create_window(self):
self.w = Window.objects.create(**{'station_id': 1})
def test_create_component(self):
self.c = Component.objects.create(**{
'type': 'Player',
'window_id': 1,
'start': datetime.datetime.now(),
'end': datetime.datetime.now(),
'json': "",
'layer': 0}
)
def test_csv_import(self):
"""Testing that standard csv imports work"""
self.assertTrue(os.path.exists(self.local_data_path))
with open(self.local_data_path) as fp:
response = self.c.post('/schedule/schedule-import/create/', {
'component': self.c,
'csv': fp,
'date_from': datetime.datetime.now(),
'date_to': datetime.datetime.now()
}, follow=False)
self.assertEqual(response.status_code, 200)
def test_record_exists(self):
new_component = Component.objects.all()
self.assertTrue(len(new_component) > 0)
And the test results
Using existing test database for alias 'default'...
.....[]
F
======================================================================
FAIL: test_record_exists (schedule.tests.ImportTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 64, in test_record_exists
self.assertTrue(len(new_component) > 0)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 6 tests in 1.250s
FAILED (failures=1)

The --keepdb option means that the database is kept. That means it's quicker to run the tests again because you don't have to recreate the table.s
However, each test method in a TestCase class is run in a transaction which is rolled back after the method has finished. Using --keepdb doesn't change this.
This means that your object created in test_create_component will not be seen by the test_record_exists test. You can either create the object inside the test_record_exists method, or in the setUp method or setUpTestData classmethod.

Related

Select related executes queries

I can't make select_related work with the following configuration:
Models:
class Text(models.Model):
author = models.ForeignKey('authors.Author',
on_delete=models.SET_NULL,
blank=True,
null=True)
Author model:
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
kwargs = {'pk': self.pk}
return reverse('author-detail', kwargs=kwargs)
View
In a view, I use the select_related function to avoid hitting the db when querying for text's authors e.g.:mytext.author:
class TextsViewTest(TestCase):
def text_view(request,
pk,
template_name='texts/detail.html'):
source_text = Text.objects.select_related('author').get(pk=pk)
return render(request, template_name,
{
'source': source_text,
})
Test
According to select_related it shouldn't hit the database when accessing the Text.author relationship, but when testing it with:
def test_layout_content_header__uses_prefetched_relationships(self):
author = Author.objects.create(name="foobar")
source_text = Text.objects.create(author=author)
context = {'source': source_text}
with self.assertNumQueries(0):
from django.template.loader import render_to_string
rendered = render_to_string("text/_content_header.html", context)
template
text/content_header.html:
{% if source.author %} by <em>{{source.author.name}}</em>{% endif %}
Output
./manage test texts.test_views shows a hit:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_layout_content_header__uses_prefetched_relationships (author.tests.test_views.TextsViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/.../text/tests/test_views.py", line 1035, in test_layout_content_header__uses_prefetched_relationships
source_text.author
File "/.../lib/python3.6/site-packages/django/test/testcases.py", line 80, in __exit__
'%d. %s' % (i, query['sql']) for i, query in enumerate(self.captured_queries, start=1)
AssertionError: 1 != 0 : 1 queries executed, 0 expected
Captured queries were:
1. SELECT "authors_author"."id", "authors_author"."name", FROM "authors_author" WHERE "authors_author"."id" = 1
----------------------------------------------------------------------
Ran 1 test in 0.489s
FAILED (failures=1)
Destroying test database for alias 'default'...
Any ideas?
It looks like you are not using your view's code inside the test. Try either to copy same query into your test, e.g.:
context = {'source': Text.objects.select_related('author').get(pk=source_text.pk)}
with self.assertNumQueries(0):
from django.template.loader import render_to_string
rendered = render_to_string("text/_content_header.html", context)
Or reuse the view code (it seems to be declared in the Test Case, right?)
with self.assertNumQueries(1):
self.text_view(MagicMock(), source_text.pk)
Although you might need to specify bit more advanced request mock, e.g. using the RequestFactory

How to pass extra keyword arguments when using Django RequestFactory?

I'm trying to write a simple unit test for a view but I'm having trouble passing extra keyword arguments to the view when I'm using RequestFactory to set up the request.
To start, here's the urlpattern:
# app/urls.py
# Example URL: localhost:8000/run/user/1/foo
urlpatterns = [
url(r'^user/(?P<uid>\d+)/(?P<uname>\w+)/$',
views.user_kw,
name='user-kw'),
]
Here's the view I'm testing:
# app/views.py
def user_kw(request, *args, **kwargs):
uid = kwargs['uid']
uname = kwargs['uname']
return render(request, 'run/user.html', context)
Finally, here's the test:
# app/tests.py
def test_user_kw(self):
factory = RequestFactory()
# ???
request = factory.post('user/')
response = views.user_kw(request)
self.assertEqual(response.status_code, 200)
As you might expect, when I run the test, I get this error:
======================================================================
ERROR: test_user_kw (run.tests.TestViews)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/jones/code/django/testing/run/tests.py", line 53, in test_user_kw
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
File "/Users/jones/code/django/testing/run/views.py", line 28, in user_kw
uid = kwargs['uid']
KeyError: 'uid'
----------------------------------------------------------------------
The Django documentation on the RequestFactory object doesn't discuss this situation. I looked at the RequestFactory code itself but I couldn't figure out how to set up the object to account for the two keyword arguments contained in the URL. I also couldn't find anything online addressing this situation.
I should add that I did manage to write a test for the case in which I used positional arguments and it works:
def test_user_pos(self):
factory = RequestFactory()
request = factory.post('user/')
response = views.user_pos(request, 1, 'foo')
self.assertEqual(response.status_code, 200)
I just can't figure out how to rewrite the test for keyword arguments. Perhaps I've been looking at the problem for too long and the answer is staring me in the face, but I just don't see it.
You can pass keyword arguments to the user_pos method the normal way:
response = views.user_kw(request, uid=1, uname='foo')
Your error message shows that you tried:
response = views.user_kw(request, {"uid": 1, "uname": "foobar"})
This isn't passing keyword arguments, it's passing a dictionary as a positional argument. Note that you can use ** to unpack the dictionary:
response = views.user_kw(request, **{"uid": 1, "uname": "foobar"})

Authenticate user in Selenium test

I'm testing user authentication in Django project with Selenium webdriver, and created a test:
class EditorBlogTest(StaticLiveServerTestCase):
def setUp(self):
self.browser = webdriver.Chrome()
def tearDown(self):
self.browser.quit()
def create_session_store(self):
user = User.objects.create(email='a#b.com', username='TestUser')
engine = import_module(settings.SESSION_ENGINE)
store = engine.SessionStore()
store[SESSION_KEY] = user.pk
store.save()
return store
def test_user_authenticated(self):
store = self.create_session_store()
self.browser.get(self.live_server_url + '/dashboard/')
self.browser.add_cookie({
'name': settings.SESSION_COOKIE_NAME,
'value': store.session_key,
'path': '/',
})
self.browser.get(self.live_server_url + '/dashboard/blog/add/')
hello_box = self.browser.find_element_by_id('hello')
self.assertIsNotNone(self.browser.get_cookie('sessionid'))
self.assertEqual('Hello, TestUser', hello_box.text)
in template there is a line:
<div id="hello">Hello, {{ user }}</div>
But the test failed:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_session_creation (functional_tests.dashboard.test_blog.EditorBlogTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/django/my_project/functional_tests/dashboard/test_blog.py", line 26, in test_user_authenticated
self.assertEqual('Hello, TestUser', hello_box.text)
AssertionError: 'Hello, TestUser' != 'Hello, AnonymousUser'
- Hello, TestUser
+ Hello, AnonymousUser
----------------------------------------------------------------------
Ran 1 test in 2.406s
FAILED (failures=1)
Destroying test database for alias 'default'...
Does anyone have an idea what's wrong with it?
I've made a deeper research of the topic and found that session has nothing to do with RequestContext, that contains {{ user }} variable (as well as the rest context). So, the easiest way to authenticate a user in Selenium test is to open login page on the site and fill the form with test user credentials. After that my test managed to pass.

Django rest framework: unit testing database issue

I am doing unit testing of the rest Apis. I am using django rest framework.
Apis are saving data into and getting data from the database. Both of the operations are not working or if it is working i am not able to see that in the databases. Apis are also using django-fsm, because of which i need same data from the db for the other tests. Tests depends on previous tests due to django-fsm. There is always state changing with the api. But now i am not able to see any data in database during test runs. Don't know where it is saving the data or in which database.
Below is my test settings:-
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': join(PROJECT_ROOT, 'run', 'db_for_testing.sqlite3'),
'TEST': {
'NAME': 'test_db_for_testing',
},
},
}
below is my api:-
class DummyView(CreateAPIView):
def post(self, request, *args, **kwargs):
data = request.data.copy()
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
order = self.model(book=serializer.data.get('book'))
order.save()
data = {
'status_code': 200,
'message': 'successfully.'
}
return Response(data, status=status.HTTP_200_OK)
As my tests depends on the previous test saving the data to db, so the other tests also fails. I am using APITestCase of rest_framework.
Help guys.
thanks in advance.
If I'm understanding your question correctly, Django "clear" database after each test (either rolling back or truncating.) So you need to write your tests accordingly.
See: https://docs.djangoproject.com/en/1.10/topics/testing/tools/#transactiontestcase
TL;DR - Solution: Use SimpleTestCase - See example below
Explanation
The thing is that the recommended test classes provided by Django for tests involving database queries, TransactionTestCase and the subclass TestCase, wraps every test in a transaction to speed up the process of resetting the database after each test. Source: Django TransactionTestCase docs
It is possible to avoid this behaviour by using SimpleTestCase which is the parent class of TransactionTestCase. You then have to specify explicitly that you want to allow database queries by setting allow_database_queries to True-
Also note that you are then responsible for any cleaning that needs to be done after the test. You can do that by overriding the tearDownClass class method. Similarly there's a setUpClass class method for any initialization prior to running the test. Remember to call the super methods. See full details in the docs
Example
from django.test import SimpleTestCase
class MyTestCase(SimpleTestCase):
allow_database_queries = True
#classmethod
def setUpClass(cls):
# Do your pre test initialization here.
super(MyTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
# Do your post test clean uphere.
super(MyTestCase, cls).tearDownClass()
def test_add_data_through_api(self):
# Add 'dat data
...
def test_work_with_data_from_previous_test(self):
# Work 'dat data
...
Use the --keepdb option when calling manage.py test:
https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-test-keepdb
It's available since django 1.8.
Hope this helps.
Here my three tests are dependent with previous one. If I run them as separate test the previous test data deleted and later one failed for lost of that data.
So I make them in two different functions both are not test function. Finally call the dependent functions from one test function.
class test_profile(APITestCase):
def setUp(self):
super_user = default_service.create_super_user()
self.application = default_service.create_oath2_application(super_user.id)
self.content_type = "application/x-www-form-urlencoded"
self.username = "user#domain.com"
self.password = "pass123"
def create_profile(self):
url = reverse('EmailSignUp')
body = {
"email": self.username,
"password": self.password,
"fullname": "Mamun Hasan"
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
self.assertEqual(data['fullname'], data['fullname'])
def get_access_token(self):
url = reverse('oauth2_provider:token')
body = {
"username": self.username,
"password": self.password,
"grant_type": self.application.authorization_grant_type,
"client_id": self.application.client_id,
"client_secret": self.application.client_secret,
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))
# print("AccessToken", data)
self.assertEqual(data['token_type'], 'Bearer')
return data
def get_profile(self, oath2_token):
url = reverse('GetProfile')
authorization = oath2_token["token_type"] + ' ' + oath2_token["access_token"]
response = self.client.get(url, CONTENT_TYPE=self.content_type, HTTP_AUTHORIZATION=authorization)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
def test_dependent(self):
self.create_profile()
oath2_token = self.get_access_token()
self.get_profile(oath2_token)
I did not find any solution to commit the previous API data. If anyone knows please comment. So I have done it this way. I don't know this is the best solution but it works and tested.

Error with Django assertJSONEqual when unit testing view

Similar to this question, I'm having problems testing a view with Django's unit testing framework. My view is very simple: it processes a form, adds an object to the database, and returns a JSONResponse. The test is equally simple, but I keep getting "First argument is not valid JSON: ''. The code actually works in my application; it just doesn't seem to work when unit testing. Any help is appreciated.
EDIT:
Traceback
======================================================================
ERROR: tearDownClass (zoning_intake.tests.AddActionTypeTest)
Traceback (most recent call last):
File "C:\Virtual\Django18\lib\site-packages\django\test\testcases.py", line 96
2, in tearDownClass
cls._rollback_atomics(cls.cls_atomics)
AttributeError: type object 'AddActionTypeTest' has no attribute 'cls_atomics'
======================================================================
FAIL: test_post_add_action_type_succeeds (zoning_intake.tests.AddActionTypeTest)
Traceback (most recent call last):
File "C:\Hg\sdcgis\zoning_intake\tests.py", line 26, in test_post_add_action_t
ype_succeeds
self.assertJSONEqual(response.content,{'result':'Success', 'msg':'Success'})
File "C:\Virtual\Django18\lib\site-packages\django\test\testcases.py", line 68
9, in assertJSONEqual
self.fail("First argument is not valid JSON: %r" % raw)
AssertionError: First argument is not valid JSON: ''
Ran 1 test in 10.757s
FAILED (failures=1, errors=1)
Preserving test database for alias 'default'...
Preserving test database for alias 'other'...
My view:
form = ActionTypeForm(request.POST)
if form.is_valid():
action = form.cleaned_data['action']
new_type = CaseRequestActionType(action=action)
new_type.save()
return JsonResponse({'result':'Success', 'msg':'Success'})
else:
return JsonResponse({'result':'Fail', 'msg':'An unknown error occurred'})
My test:
class AddActionTypeTest(TestCase):
if django.VERSION[:2] >= (1, 7):
# Django 1.7 requires an explicit setup() when running tests in PTVS
#classmethod
def setUpClass(cls):
django.setup()
def test_post_add_action_type_fails(self):
response = self.client.post(reverse('zoning:add_action_type'))
self.assertEqual(response.status_code, 302)
self.assertJSONEqual(force_text(response.content), {'result':'Fail', 'msg':'An unknown error occurred'})
So it turns out that the issue is very simple, and the 302 status code is the key to understanding why I had this issue. I have the #login_required decorator on my view, so when I ran the test WITHOUT having logged in a user, I'm redirected to my login view. Since the login view returns html, not JSON, my response is not valid JSON, and the status code returns 302 instead of the expected 200. I needed to override the setUp method to create a user in the database and then call login in the test itself in order for my test to work properly and my status code to return 200. Thanks to #Shang Wang for assistance
Complete View:
#login_required
def add_action_type(request):
if request.method == 'GET':
...
else:
form = ActionTypeForm(request.POST)
if form.is_valid():
action = form.cleaned_data['action']
new_type = CaseRequestActionType(action=action)
new_type.save()
return JsonResponse({'result':'Success', 'msg':'Success'})
else:
return JsonResponse({'result':'Fail', 'msg':'An unknown error occurred'})
Updated test:
class AddActionTypeTest(TestCase):
#classmethod
def setUp(self):
self.user = User.objects.create_user(username='shawn', email='shawn#...com', password='top_secret')
def test_post_add_action_type_fails(self):
self.client.login(username=self.user.username, password='top_secret')
response = self.client.post(reverse('zoning:add_action_type'))
self.assertJSONEqual(force_text(response.content), {'result':'Fail', 'msg':'An unknown error occurred'})