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 \.
Related
While running python's unittest in my Flask application, I am returned a 404 status code when using multiple TestCase classes.
I've tried flask_testing and have gotten similar issues. I opted for unittest because of its greater popularity and availability to find documentation online.
test_global.py
from server import create_app
class Global(unittest.TestCase):
def setUp(self):
self.app = create_app(testing=True)
self.client = self.app.test_client()
self.client.testing = True
# tests different cookie redirect
def test_different_cookie_redirect(self):
self.client.set_cookie('127.0.0.1', 'lang', 'en')
response = self.client.get('/fr')
# this passes
self.assertEqual(response.status_code, 302)
The above works as intended. If the cookie is different, the page should redirect. The problem happens when I want to add another class.
class Index(unittest.TestCase):
def setUp(self):
self.app = create_app(testing=True)
self.client = self.app.test_client()
self.client.testing = True
# tests same cookie redirect
def test_same_cookie_redirect(self):
self.client.set_cookie('127.0.0.1', 'lang', 'fr')
response = self.client.get('/fr')
# this returns a 404 and fails the test
self.assertEqual(response.status_code, 200)
This is the error
Traceback (most recent call last):
File "/test_global.py", line 55, in test_same_cookie_redirect
self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200
If I remove the Global class, the Index test then works and returns a status_code of 200. Why can't both work at the same time?
The reason I opted to have multiple classes is to be able to split my code in different files and run python -m unittest discover to handle them all.
So I am having issues with my website in production and figured it was something with Nginx while in production. But I have boiled it down to another issue, that I am not quite sure how to define.
The issue at hand is when using the send_mail function in Django (1.10.x). I can send emails perfectly when I run my function code from the terminal, just by typing it out while in a python shell. But when I try to run it using RequestFactory and running the given function with the request, I get an odd error.
This error on my terminal screen is much more clear than the blank 500 server error I receive on my website.
I have tried different email setups, even changing the email backend to console in the settings, and nothing is working.
Code that works, after opening up my shell ./manage.py shell_plus
>>> from django.core.mail import send_mail
>>> name = 'Test User'
>>> contact_email = 'testing#test.com'
>>> contact_phone = '123-456-7890'
>>> subject = 'Message from: %s, %s' % (name, contact_phone)
>>> message = 'This is a test being sent from the backend console.'
>>> to = 'user#test.com' # changed for anonymity
>>> send_mail(subject, message, contact_email, [to], fail_silently=False)
>>> 1 # this is what's returned
Code that doesn't work
>>> from django.test import RequestFactory
>>> factory = RequestFactory()
>>> from views import contact # the view that runs my send_mail function
>>> request = factory.get(contact)
>>> request.method = 'POST' # needs to be POST method to trigger my view function that triggers the email function
>>> contact(request) # this is where things go south
Let me clarify something before I post my errors. When contact(request) is run, it triggers a function called contact_form that contains the exact same code that is working in the above code that works segment.
Here is the long winded error that I get back, and because of it, I am at a loss.
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/root/test/website/views.py", line 46, in contact
request = utils.contact_form(request)
File "/root/test/website/utils.py", line 20, in contact_form
contact_phone = '123-456-7890'
File "/root/test/local/lib/python2.7/site-packages/django/core/mail/__init__.py", line 62, in send_mail
return mail.send()
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/message.py", line 348, in send
return self.get_connection(fail_silently).send_messages([self])
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/backends/smtp.py", line 111, in send_messages
sent = self._send(message)
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/backends/smtp.py", line 125, in _send
message = email_message.message()
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/message.py", line 307, in message
msg = SafeMIMEText(self.body, self.content_subtype, encoding)
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/message.py", line 214, in __init__
MIMEText.__init__(self, _text, _subtype=_subtype, _charset=_charset)
File "/usr/lib/python2.7/email/mime/text.py", line 30, in __init__
self.set_payload(_text, _charset)
File "/root/test_env/local/lib/python2.7/site-packages/django/core/mail/message.py", line 224, in set_payload
for l in payload.splitlines()
AttributeError: 'NoneType' object has no attribute 'splitlines'
Minor Tests After Error
So I noticed this line: File "/root/test/website/utils.py", line 20, in contact_form
contact_phone = '123-456-7890' and decided to comment it out in the function. I received the exact same error, just with a # before the contact_phone line.
I even deleted the contact_phone line entirely, and it just displayed the next line in code in that same error.
EDIT
Here is the breakdown of request in case it is of any help
>>> request.environ
{u'HTTP_COOKIE': u'', u'wsgi.multithread': False,
u'SCRIPT_NAME': u'',
u'wsgi.input': <django.test.client.FakePayload object at 0x7f12e169d850>,
u'REQUEST_METHOD': 'GET',
u'PATH_INFO': u'<function contact at 0x7f12e226a050>',
u'SERVER_PROTOCOL': 'HTTP/1.1',
u'QUERY_STRING': '',
u'wsgi.version': (1, 0),
u'SERVER_NAME': 'testserver',
u'REMOTE_ADDR': '127.0.0.1',
u'wsgi.run_once': False, u'wsgi.errors': <_io.BytesIO object at 0x7f12e16329b0>,
u'wsgi.multiprocess': True,
u'wsgi.url_scheme': 'http',
u'SERVER_PORT': '80', u'CSRF_COOKIE': u'IicQMalE3nSO682lHcZQG3kI51X5f1P1wQwfFzLdv3EFjM2KdnUrlayjjsbrsOct',
u'CSRF_COOKIE_USED': True}
MORE TESTING
So I have moved the function that I am using to send mail, and placed it directly inside of the view function. But I did not even place it as a function itself, but rather just some variable assigning (with test values) and the send_mail function itself. Still getting a 500 error, and this is without even attempting to parse information from an HTML form.
You are using RequestFactory wrong. It accepts a URL path (e.g., /), and not a view function which is what you are passing to it. You need to use the factory to generate a request object, and then pass that to your view function. Something like this:
from django.test import RequestFactory
request = RequestFactory().post('/') # Use post() instead of get() if you're testing a post request
# Pass this request object to your view function
response = contact(request)
The example in the documentation for how to use RequestFactory is worth looking at in this context.
I have a simple test case which checks that POST request with valid data returns an HTML response with 200 status code:
class PostRequestTestCase(TestCase):
def test_valid_post_request(self):
response = self.client.post('/foo/', data={'text': 'bar'})
self.assertEqual(response.status_code, 200)
Here is the view foo which is triggered for that request:
logger = logging.getLogger(__name__)
# decorator to trace enter and exit events
enter_exit_tracer = enter_exit_Tracer(logger)
#enter_exit_tracer
def foo(request):
if request.method == 'POST':
#
print('request.POST:', request.POST)
#
# some stuff
where #enter_exit_tracer is a decorator to trace entering/exiting a function:
def enter_exit_Tracer(logger):
def middle(f):
def inner(*args, **kwargs):
session_string = args[-1] if 'session_key' in args[-1] else '[]'
logger.debug('%s Enter %s.', session_string, f.__name__)
result = f(*args, **kwargs)
logger.debug('%s Exit %s.', session_string, f.__name__)
return result
return inner
return middle
It turns out that when I add this decorator to foo function, then the POST data sent via self.client.post are actually not passed to foo. They are missing in the test request - so my test fails:
DEBUG: [] Enter foo.
request.POST: <QueryDict: {}>
ERROR: Invalid form
F
======================================================================
FAIL: test_valid_post_request (textstat.tests.PostRequestTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/Pro/myapp/tests.py", line 111, in test_valid_post_request
self.assertEqual(response.status_code, 200)
AssertionError: 400 != 200
----------------------------------------------------------------------
Ran 1 test in 0.018s
FAILED (failures=1)
We see request.POST: <QueryDict: {}> and eventually this leads to ERROR: Invalid form.
At the same time when I do the similar POST request but via a web browser everything works fine - the form gets populated with data, the web page is rendered ok and #enter_exit_tracer does its logging as expected.
If I comment out #enter_exit_tracer decorator then everything works also ok and the test is passed:
request.POST: <QueryDict: {'text': ['bar']}>
.
----------------------------------------------------------------------
Ran 1 test in 0.007s
OK
The questions:
why the request.POST data are not passed to the view with the decorator in case of self.client.post request?
if anything wrong with the decorator - why then the web request works ok?
is it possible to keep the decorator and makeself.client.post pass the data to the view?
It turns out that the decorator has a TypeError error in this line:
session_string = args[-1] if 'session_key' in args[-1] else '[]'
So when args[-1] is not str then this should not work. Fixing it with str(args[-1]) resolves the whole issue with missing POST data.
It is weird though that self.client.post does not raise any TypeError and session_string was somehow calculated. Instead of this POST data were not sent to the view without any errors or hint warnings. POST data were just disappeared. At the same time the same code worked ok when POST request was sent from web browser, so the error remained unnoticed.
I'm implementing unit testing for a django handler. I have been trying to mock/patch the api connection instance but keep getting the error:
super(MTurkConnection, self).__init__(aws_access_key_id,
TypeError: must be type, not MagicMock
My test code looks like this and is living in tests/test_mturk_handler.py:
class Mocked_Connection(MTurkConnection):
pass
#patch('mturk.handlers.MTurkHandler.boto.mturk.connection.MTurkConnection')
def test_can_pull_data(self, mock_api):
mock_api.return_value = Mocked_Connection()
response = self.client.post(reverse('mechanical'), self.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
The API call is beign made in this section of code and is living in handlers/MTurkHandler.py:
from boto.mturk.connection import MTurkConnection
import boto
...
if serializer.is_valid():
# instantianting connection
mturk_connection = MTurkConnection(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, host=HOST)
I have tried several return values but I keep getting the same error. Any ideas what's the right way to do it??
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)