Select related executes queries - django

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

Related

Django 2 Test: Created object cannot be gotten for tests

I've read about building programs starting with tests, so I thought I'd give it a shot. I've made my initial project and app and a model, but when I try to run a test, I get a "Does not exist" error. I've run the migrations, is there more I have to do before I can run a test? Here's my code:
models.py
from django.db import models
class Name(models.Model):
first_name = models.CharField(
"First Name",
max_length=100,
)
middle_name = models.CharField(
"Middle Name or Initial",
max_length=100,
default='',
)
last_name = models.CharField(
"Last Name",
max_length=200,
)
def __str__(self):
return f'{self.last_name}, {self.first_name}'
tests.py
from django.test import TestCase
from contacts.models import Name
class TestNameModel(TestCase):
#classmethod
def setupTestData(cls):
Name.objects.create(first_name='Banny', last_name='Banvil')
def test_name_exists(self):
name = Name.objects.get(id=1)
print (name)
And the error that I get:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_name_exists (contacts.tests.TestNameModel)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/CrazyCarl/Software/contact/contacts/tests.py", line 12, in test_name_exists
name = Name.objects.get(id=1)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/query.py", line 403, in get
self.model._meta.object_name
contacts.models.DoesNotExist: Name matching query does not exist.
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)
Destroying test database for alias 'default'...
I tried making an object in the terminal to see if it was working:
>>>from contacts.models import Name
>>>Name.objects.create(last_name='John', first_name='Smith')
<Name: John, Smith>
>>>Name.objects.all()
<QuerySet [<Name: John, Smith>]>
>>>n = Name.objects.get(id=1)
>>>print (n)
John, Smith
What am I missing?
It's better to use fixtures. Basically you have nothing in your db when you try do get(id=1)
Also fixtures will save your time in the feature. Good luck!
It’s setUpTestData not setupTestData

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

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.

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

mocking a method on django model using post_save signal

So here's something I'm trying to figure out. I've got a method that is triggered by post_save
for this "Story" model. Works fine. What I need to do is figure out how to mock out the test, so I can fake the call and make assertions on my returns. I think I need to patch it somehow, but I've tried a couple different ways without much success. Best i can get is a object instance, but it ignores values I pass in.
I've commented in my test where my confusion lies. Any help would be welcome.
Here's my test:
from django.test import TestCase
from django.test.client import Client
from marketing.blog.models import Post, Tag
from unittest.mock import patch, Mock
class BlogTestCase(TestCase):
fixtures = [
'auth-test.json',
'blog-test.json',
]
def setUp(self):
self.client = Client()
def test_list(self):
# verify that we can load the list page
r = self.client.get('/blog/')
self.assertEqual(r.status_code, 200)
self.assertContains(r, "<h1>The Latest from Our Blog</h1>")
self.assertContains(r, 'Simple JavaScript Date Formatting')
self.assertContains(r, 'Page 1 of 2')
# loading a page out of range should redirect to last page
r = self.client.get('/blog/5/', follow=True)
self.assertEqual(r.redirect_chain, [
('http://testserver/blog/2/', 302)
])
self.assertContains(r, 'Page 2 of 2')
# verify that unpublished posts are not displayed
with patch('requests') as mock_requests:
# my futile attempt at mocking.
# creates <MagicMock> object but not able to call return_values
mock_requests.post.return_value = mock_response = Mock()
# this doesn't get to the magic mock object. Why?
mock_response.status_code = 201
p = Post.objects.get(id=5)
p.published = False
# post_save signal runs here and requests is called.
# Needs to be mocked.
p.save()
r = self.client.get('/blog/')
self.assertNotContains(r, 'Simple JavaScript Date Formatting')
Here's the model:
from django.db import models
from django.conf import settings
from django.db.models import signals
import requests
def update_console(sender, instance, raw, created, **kwargs):
# ignoring raw so that test fixture data can load without
# hitting this method.
if not raw:
update = instance
json_obj = {
'author': {
'alias': 'the_dude',
'token': 'the_dude'
},
'text': update.description,
}
headers = {'content-type': 'application/json'}
path = 'http://testserver.com:80/content/add/'
request = requests(path, 'POST',
json_obj, headers=headers,
)
if request.status_code < 299:
story_id = request.json().get('id')
if story_id:
# disconnect and reconnect signal so
# we don't enter recursion-land
signals.post_save.disconnect(
update_console,
sender = Story, )
update.story_id = story_id
update.save()
signals.post_save.connect(
update_console,
sender = Story, )
else:
raise AttributeError('Error Saving to console, '+ request.text)
class Story(models.Model):
"""Lets tell a story"""
story_id = models.CharField(
blank=True,
max_length=10,
help_text="This maps to the id of the post"
)
slug = models.SlugField(
unique=True,
help_text="This is used in URL and in code references.",
)
description = models.TextField(
help_text='2-3 short paragraphs about the story.',
)
def __str__(self):
return self.short_headline
# add/update this record as a custom update in console
signals.post_save.connect(update_console, sender = Story)
You need to patch requests in the module where it is actually used, i.e.
with patch('path.to.your.models.requests') as mock_requests:
mock_requests.return_value.status_code = 200
mock_requests.return_value.json.return_value = {'id': story_id'}
...
The documentation offers more detailed explanations on where to patch:
patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
Here, you need to patch the name requests inside the models module, hence the need to provide its full path.

Django and nosetests : DoesNotExist: <object> matching query does not exist

I'm just starting a small app on django. Its aim, for now, is just to manage testers (aka users) and teams. here's my model.py :
from django.db import models
class Team(models.Model):
name = models.CharField(max_length=200)
def __unicode__(self):
return self.name
def get_testers(self):
return self.tester_set.all()
class Tester(models.Model):
team = models.ForeignKey(Team)
visa = models.CharField(max_length=3)
privileged = models.BooleanField()
def __unicode__(self):
return self.visa
I'm trying to write a test for the "get_testers" function.
Here it is :
from models import Team, Tester
def testTeamGetTesters_test():
t = list(Team.objects.get(id=2L).get_testers())
a = Tester(visa = 'a', privileged = True)
b = Tester(visa = 'b', privileged = True)
assert(t[0].visa == a.visa and t[0].privileged == a.privileged and t[1].visa == b.visa and t[1].privileged == b.privileged)
But when I run :
$ python manage.py test tmg
I get this error :
nosetests --verbosity 1 tmg
E
======================================================================
ERROR: tempsite.tmg.tests.testTeamGetTesters_test
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.6/nose/case.py", line 183, in runTest
self.test(*self.arg)
File "/home/charlie/code/tempsite/../tempsite/tmg/tests.py", line 8, in testTeamGetTesters_test
t = list(Team.objects.get(id=2L).get_testers())
File "/usr/lib/pymodules/python2.6/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/usr/lib/pymodules/python2.6/django/db/models/query.py", line 341, in get
% self.model._meta.object_name)
DoesNotExist: Team matching query does not exist.
So, I wrote just about the same test, but directly runable :
from models import Team, Tester
t = list(Team.objects.get(id=2L).get_testers())
a = Tester(visa = 'a', privileged = True)
b = Tester(visa = 'b', privileged = True)
print "%r" % (t[0].visa == a.visa and t[0].privileged == a.privileged and t[1].visa == b.visa and t[1].privileged == b.privileged)
...And when I run it :
$ python tests.py
True
This is very confusing... I checked the database, the objects are all perfectly retrieved, but I still get this error...
Are you creating the Team object with id 2 somewhere in your test? Don't forget tests start with a blank database.