Django test failing only when its module is run individually - python

I'm running tests in a Django project called lucy-web. If I run a simple python manage.py test, all tests pass:
(venv) Kurts-MacBook-Pro:lucy-web kurtpeek$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........................
----------------------------------------------------------------------
Ran 24 tests in 22.097s
OK
Destroying test database for alias 'default'...
However, if I try to run a particular test defined in lucy_web/tests/test_schedule_request.py, it fails:
(venv) Kurts-MacBook-Pro:lucy-web kurtpeek$ python manage.py test lucy_web.tests.test_schedule_request
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.F
======================================================================
FAIL: test_send_schedule_request (lucy_web.tests.test_schedule_request.ScheduleRequestTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/kurtpeek/Documents/Dev/lucy/lucy-web/venv/lib/python3.6/site-packages/freezegun/api.py", line 495, in wrapper
result = func(*args, **kwargs)
File "/Users/kurtpeek/Documents/Dev/lucy/lucy-web/lucy_web/tests/test_schedule_request.py", line 44, in test_send_schedule_request
self.assertEqual(mail.outbox[0].body, expected_body)
AssertionError: 'Hi!\[17 chars]ily 1) wants to schedule a Birth Preparation a[102 chars] AM.' != 'Hi!\[17 chars]ily 12) wants to schedule a Birth Preparation [103 chars] AM.'
Hi!
- Test Test (family 1) wants to schedule a Birth Preparation and Preferences session within the next day or two.
+ Test Test (family 12) wants to schedule a Birth Preparation and Preferences session within the next day or two.
? +
Test requested this session on 01/01/12 at 09:00 AM.
----------------------------------------------------------------------
Ran 2 tests in 2.365s
FAILED (failures=1)
Destroying test database for alias 'default'...
Here is the offending test as defined in test_schedule_request.py:
import json
from django.conf import settings
from django.contrib.auth.models import User
from django.core import mail
from django.test import Client, TestCase
from freezegun import freeze_time
from ..models import Company, Family, Package, SessionType, Session, SessionCategory
class ScheduleRequestTest(TestCase):
def setUp(self):
self.client = Client()
self.url = '/api/v1.0/sessions/send_schedule_request/'
#freeze_time("2012-01-01 17:00:00")
def test_send_schedule_request(self):
user = User.objects.create_user('john', 'lennon#thebeatles.com', 'johnpassword')
user.first_name = "Test"
user.last_name = "Test"
user.save()
company = Company.objects.create(name='test company')
package = Package.objects.create(name='test package', company=company)
family = Family.objects.create(employee_user=user, employee_first_name='Test', employee_last_name='test', employee_email='test#test.net', package=package, point_of_contact='Employee')
category = SessionCategory.objects.create(name="Birth Prep")
session_type = SessionType.objects.create(category=category, title='Birth Preparation and Preferences', recommended_timing='32-36 weeks', min_duration=60, max_duration=90, allow_virtual=True, allow_in_person=True, description='some stuff', what_to_expect='other stuff')
session = Session.objects.create(session_number=1, family=family, session_type=session_type, location=Session.OTHER, other_location='spa', feedback_expert='YES', feedback_session='YES')
self.client.login(username='john', password='johnpassword')
response = self.client.post(self.url, json.dumps({'session_id': session.id, 'timeframe': 'within the next day or two'}), content_type="application/json")
self.assertEqual(response.status_code, 200)
# Check that session status has been updated
self.assertEqual(Session.objects.filter(id=session.id)[0].status, 'Times Requested')
# Check that two emails have been sent.
self.assertEqual(len(mail.outbox), 2)
# Verify that the contents of the emails are correct
self.assertEqual(mail.outbox[0].subject, 'LUCY: Request to Schedule Session')
self.assertEqual(mail.outbox[0].from_email, settings.DEFAULT_FROM_EMAIL)
self.assertEqual(mail.outbox[0].to, [settings.CUSTOMER_SERVICE_EMAIL])
self.assertEqual(mail.outbox[0].reply_to, [user.email])
expected_body = "Hi!\n\nTest Test (family 12) wants to schedule a Birth Preparation and Preferences session within the next day or two.\n\nTest requested this session on 01/01/12 at 09:00 AM."
self.assertEqual(mail.outbox[0].body, expected_body)
self.assertEqual(mail.outbox[1].subject, 'LUCY: Request to Schedule Session')
self.assertEqual(mail.outbox[1].from_email, settings.DEFAULT_FROM_EMAIL)
self.assertEqual(mail.outbox[1].to, [user.email])
self.assertEqual(mail.outbox[1].reply_to, [settings.CUSTOMER_SERVICE_EMAIL])
expected_body = "Hi Test,\n\nWe received your request for Birth Preparation and Preferences and our team is currently working on scheduling it for you. We will confirm the expert, date & time within 48 hours (or sooner if your request was urgent) via this email address.\n\nIf you have any questions, simply reply to this email and someone from our team will get back to you ASAP!\n\nBest,\nLUCY team"
self.assertEqual(mail.outbox[1].body, expected_body)
I've checked using the --verbosity option that this test was run successfully when I was running all tests. The only explanation I can think of is that the tests are somehow not isolated from each other, and the success of one depends on the setUp of the other.
Could this be the case? Do I perhaps have to write some tearDown methods in the previous test cases? Or does Django run each test on a new database, so that this should not be necessary?

It appears that the code that created the expected_body refers to the an id or other similar field. If the full test suite is run it will keep using the next unique number when it creates the test (in the full suite it will be the 12th creation).
When running the test by itself it is therefore the 1st creation.
You should refer to the id when creating the string e.g.:
expected_body = "Hi!\n\nTest Test (family %s) wants to schedule a Birth Preparation and Preferences session within the next day or two.\n\nTest requested this session on 01/01/12 at 09:00 AM." % family.id

Related

Unit testing for ClassMethod of a model in Flask

I have two databases: one is master and for testing, I have another database master_test
To retrieve a total price from a table I have created a #classmethod in a model. This method helps me get the sum of the price filtering by month and year. Here is the class method:
#classmethod
def get_total_book_price(cls, id):
query = Book.query.with_entities(
func.sum(Book.price).label("price")
).filter(
extract('year', Book.created_at) >= datetime.date.today().year,
extract('month', Book.created_at) >= datetime.date.today().month
).filter(
Book.id == id
).all()
return query[0].price
This query works nicely. But when I run this for test case its showing master database does not exist. It should find the master_test database instead of the master database.
Here is the test code:
def test_get_total_book_price(self):
id = 1
response = Book.get_total_book_price(id)
if not response:
self.assertEqual(response, False)
self.assertEqual(response, True)
It's showing the ERROR:
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) FATAL: database "master"
does not exist
(Background on this error at: http://sqlalche.me/e/e3q8)
----------------------------------------------------------------------
Ran 34 tests in 2.011s
FAILED (errors=1)
ERROR: Job failed: exit code 1
Some other test cases are working nicely with master_test. But for this test why it is looking for master database ?
You have to provide a context for your test function. The best way to do this is with factories. An excellent description how to do this is: http://alanpryorjr.com/2019-05-20-flask-api-example/ . If you have an app and a db fixture, you can just use it in your test function:
from test.fixtures import app, db
def test_get_total_book_price(self, db):
id = 1
response = Book.get_total_book_price(id)
if not response:
self.assertEqual(response, False)
self.assertEqual(response, True)
Yes, the only difference is the db in the function call. I cannot say why your other tests are working. My best guess is that your failing test is executed after the test context is destroyed. Better be explicit about your app context every single time.
If everything else fails (and you have access to your app with the right database connection) you could push the context manually with:
from foo import app
def test_get_total_book_price(self, app):
app.app_context().push()
id = 1
response = Book.get_total_book_price(id)
if not response:
self.assertEqual(response, False)
self.assertEqual(response, True)
I want to stress that you should use factories for your testing.
Reference: https://flask.palletsprojects.com/en/1.0.x/appcontext/

Saving a model in a separate thread

In my simple webapp I have a model called Document. When the document is created it is empty. The user can then request to generate it, which means that its content is filled with data. Since this generating step can take some time, it is an asynchronous request: the server starts a thread to generate the document, the user obtains a quick response saying that the generation process started, and after some time the generation is over and the database is updated.
This is the code that describes the model:
import time
from threading import Thread
from django.db import models
STATE_EMPTY = 0
STATE_GENERATING = 1
STATE_READY = 2
class Document(models.Model):
text = models.TextField(blank=True, null=True)
state = models.IntegerField(default=STATE_EMPTY, choices=(
(STATE_EMPTY, 'empty'),
(STATE_GENERATING, 'generating'),
(STATE_READY, 'ready'),
))
def generate(self):
def generator():
time.sleep(5)
self.state = STATUS_READY
self.text = 'This is the content of the document'
self.state = STATE_GENERATING
self.save()
t = Thread(target=generator, name='GeneratorThread')
t.start()
As you can see, the generate function changes the state, saves the document and spawns a thread. The thread works for a while (well,... sleeps for a while), then changes and state and the content.
This is the corresponding test:
def test_document_can_be_generated_asynchronously(self):
doc = Document()
doc.save()
self.assertEqual(STATE_EMPTY, doc.state)
doc.generate()
self.assertEqual(STATE_GENERATING, doc.state)
time.sleep(8)
self.assertEqual(STATE_READY, doc.state)
self.assertEqual('This is the content of the document', doc.text)
This test passes. The document object correctly undergoes all expected changes.
Unfortunately, the code is wrong: after changing the content of the document, it is never saved, so the changes are not persistent. This can be verified by adding the following line to the test:
self.assertEqual(STATE_READY, Document.objects.first().state)
This assertion fails:
self.assertEqual(STATE_READY, Document.objects.first().state)
AssertionError: 2 != 1
The solution is simple: just add self.save() at the end of the generator function. But this results in different kind of problem:
Destroying test database for alias 'default'...
Traceback (most recent call last):
File ".../virtualenvs/DjangoThreadTest-elBGAiyX/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
return self.cursor.execute(sql)
psycopg2.errors.ObjectInUse: database "test_postgres" is being accessed by other users
DETAIL: There is 1 other session using the database.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
File ".../virtualenvs/DjangoThreadTest-elBGAiyX/lib/python3.7/site-packages/django/db/backends/utils.py", line 82, in _execute
return self.cursor.execute(sql)
django.db.utils.OperationalError: database "test_postgres" is being accessed by other users
DETAIL: There is 1 other session using the database.
The problem seems related to the save() placed in a different thread. The engine used does not seem to affect the result: I obtain almost identical error messages when using postgresql (as shown) and sqlite (in that case the error is along the lines of "The database table is locked").
Some similar questions obtain replies such as "Just use Celery to manage heavy processing tasks". I would rather understand what I'm doing wrong and how to solve it using Django tools. In fact, there is no heavy processing, nor the need to scale to large users (the webapp is to be used by one user at the time)
When you spawn a new thread, Django creates a new connection to the database for that thread. Normally, all connections are closed in the start/end of the request cycle and at the end of a test run. But if the thread is manually spawned, there is no code to close connection - the thread ends, its local data is destroyed but the connection is not closed on database side properly (connections is stored in thread.local object if you are interested).
So, to solve the issue you have to manually close connections at the end of a thread.
from django.db import connection
def generate(self):
def generator():
time.sleep(5)
self.state = STATUS_READY
self.text = 'This is the content of the document'
self.save()
connection.close()
self.state = STATE_GENERATING
self.save()
t = Thread(target=generator, name='GeneratorThread')
t.start()

Unittest run a test only if the previous one is a success

I am writing test cases for my CRUD flask app https://github.com/Leo-g/Flask-Skeleton/blob/master/tests.py
I want to ensure that the update and delete tests run only if the add tests succeeds.
def test_add(self):
rv = self.app.post('/users/add', data=dict(name = 'test name', email = 'test#email.com'), follow_redirects=True)
assert 'Add was successful' in rv.data.decode('utf-8')
def test_Update(self):
with app.app_context():
id = Users.query.first().id
rv = self.app.post('/users/update/{}'.format(id), data=dict(name = 'test name update', email = 'test#email.update'), follow_redirects=True)
assert 'Update was successful' in rv.data.decode('utf-8')
While reading the docs I see that this can be done via the unittest.skip decorator but I am not sure How I can implement it.
You can use the --failfast command-line option when running the tests to make the test suite stop after a single test fails. For example, if you normally run the tests with the command python -m unittest you can change it to python -m unittest --failfast.

Django Test Client does not create database entries

I'm creating unit tests for my views using Django's built-in Test Client to create mock requests.
The view I'm calling should create an object in the database. However, when I query the database from within the test method the object isn't there - it either hasn't been created or has been discarded on returning from the view.
Here's the view:
def apply_to_cmp(request, campaign_id):
""" Creates a new Application to 'campaign_id' for request.user """
campaign = Campaign.objects.get(pk = campaign_id)
if not Application.objects\
.filter(campaign = campaign, user = request.user)\
.exists():
application = Application(**{'campaign' : campaign,
'user' : request.user})
application.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
This is the test that calls it:
def test_create_campaign_app(self):
""" Calls the method apply_to_cmp in .views """
c = Client()
c.login(username = self.username, password = self.password)
url = '/campaign/' + self.campaign.id + '/apply/'
response = c.get(url)
# Check whether request was successful (should return 302: redirect)
self.assertEqual(response.status_code, 302)
# Verify that an Application object was created
app_count = Application.objects\
.filter(user = self.user, campaign = self.campaign)\
.count()
self.assertEqual(app_count, 1)
This is the output from the running the test:
Traceback (most recent call last):
File "/test_views.py", line 40, in test_create_campaign_app
self.assertEqual(app_count, 1)
AssertionError: 0 != 1
The method apply_to_cmp is definitely being called, since response.status_code == 302, but still the Application object is not created. What am I doing wrong?
Edit: Solution
Client.login failed because the login system was not properly initialised in the setUp method. I fixed this by calling call_command('loaddata', 'initial_data.json') with initial_data.json containing the setup for the login system. Also, HttpResponseRedirect(request.META.get('HTTP_REFERER')) didn't work for obvious reasons. I changed that bit to
if request.META.get('HTTP_REFERER'):
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
return HttpResponse()
And therefore the test to
self.assertEqual(response.status_code, 200)
Thanks for your help!
Nothing stands out as particularly wrong with your code - but clearly either your test case or the code your are testing is not working the way you think. It is now time to question your assumptions.
The method apply_to_cmp is definitely being called, since response.status_code == 302
This is your first assumption, and it may not be correct. You might get a better picture of what is happening if you examine other details in the response object. For example, check the response.redirect_chain and confirm that it actually redirects where you expect it to:
response = c.get(url, follow=True)
self.assertEqual(response.redirect_chain, [<expected output here>])
What about other details? I can't see where self.username and self.password are defined from the code you provided. Are you 100% sure that your test code to login worked? c.login() returns 'True' or 'False' to indicate if the login was successful. In my test cases, I like to confirm that the login succeeds.
login_success = c.login(username = self.username, password = self.password)
self.assertTrue(login_success)
You can also be a bit more general. You find nothing if you check Application.objects.filter(user=self.user, campaign=self.campaign), but what about checking Application.objects.all()? You know that a specific item isn't in your database, but do you know what is stored in the database (if anything at all) in the test code at that time? Do you expect other items in there? Check to confirm that what you expect is true.
I think you can solve this one, but you'll need to be a bit more aggressive in your analysis of your test case, rather than just seeing that your app_count variable doesn't equal 1. Examine your response object, put in some debug statements, and question every assumption.
First of all, if you are subclassing from django.test.TestCase, please take in consideration the fact that each test is wrapped into transactions (official docs).
Then, you can add db logging to your project to see whether there was a hit to the database or not (official docs).
And finally be sure that you're using correct lookups at this line: filter(user = self.user, campaign = self.campaign)

How can I interactively explore why a test is failing?

I have a test that is failing with:
======================================================================
FAIL: test_register_should_create_UserProfile (APP.forum.tests.test_views.UserTestCAse)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/Bryan/work/app/../app/forum/tests/test_views.py", line 25, in test_register_should_create_UserProfile
self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200
Here is the test:
class UserTestCAse(TestCase):
def test_register_should_create_UserProfile(self):
from django.test.client import Client
c = Client()
# I'm skipping account/signin/ because that requires me to visit Google.
response = c.post('account/signin/complete/', {'username': 'john', "email":'john#beatles.com', u'bnewaccount': 'Signup'})
# request.POST from pdb() session with breakpoint in register()
# <QueryDict: {u'username': [u'john'], u'email': [u'john#beatles.com'], u'bnewaccount': [u'Signup']}>
#How do I inspect what is breaking in the test case?
#How do I run the test client in the shell?
self.assertEqual(response.status_code, 200)
user = User.objects.get(username ='john')
self.assertTrue(user.get_profile())
Is there anyway that I can see why this response is not returning 200?
I tried to use the TestClient() in the shell, but that didn't work:
In [1]: from django.test.client import Client
In [2]: c = Client()
In [3]: response = c.post('account/signin/complete/', {'username': 'john', "email":'john#beatles.com', u'bnewaccount': 'Signup'})
---------------------------------------------------------------------------
KeyError: 'tried'
This doesn't look right.
user = User.objects.get('username'=='john')
If you want to query, you have to write queries in the style shown in the tutorial
http://docs.djangoproject.com/en/1.1/topics/db/queries/#topics-db-queries
user = User.objects.get( username = 'john' )
for example.
To debug, you can run things at the command line. That's what we do. The Django tutorial shows all examples as if they're typed interactively at the command line.
>>> from myapp.models import Client
>>> Client.objects.get( name = 'value' )
etc.
You don't generally try to run the unit tests from the command line. That's hard to do because of all the things the unit test framework does for you.
You generally just step through the application view function statements one at a time to be sure your view functions will actually work.
If you're getting an unexpected 404, the Django error page would show you the error (assuming DEBUG is True). In which case, you could just print response.content to show that traceback, and cut and paste that into a browser.
Also, don't forget you can break into a running test with the interactive debugger, pdb, just like any other form of running code, so you could inspect the response dynamically. Or even, put the breakpoint before the post, so you can step through the view.
However, on a second look, I suspect your url is not matching because you are missing an initial \.

Categories

Resources