I am wondering if someone could help me. I am trying to test some views in a Django restaurant bookings system app I have created. I am doing well with jut testing the views but now I want to test the CRUD functionality of certain pages. In particular the create a booking on the bookings page and then redirect it back to the home page once the booking was successful (as is what happens on the site)
I just can't seem to figure out how to do it. Here is my current code. If someone could point me in the right direction that would be great. Thanks
setUp:
class TestViews(TestCase):
"""
Testing of the views taken from
the views.py file.
All HTTP testing includes the base.html template
as well as the view being tested to make sure everything
is being tested as it would appear for a user
"""
def setUp(self):
testing_user = User.objects.create_user(
username='JohnSmith',
first_name='John',
last_name='Smith',
email='johnsmith#email.com',
password='RandomWord1'
)
Booking.objects.create(
user=testing_user,
name='John Smith',
email_address='johnsmith#email.com',
phone='123654789',
number_of_people='2',
date='2022-10-20',
time='19:00',
table='Window',
occasion='none'
)
test:
def test_add_booking(self):
self.log_in()
response = self.client.post('/bookings', {Booking: Booking})
self.assertRedirects(response, '/')
You can check the status's code of your response, then also compare the response content.
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No bookins.")
Or even to be more precisely self.assertQuerysetEqual(response.context["the name"], ["with real content"]
I've enabled login authentication for my django app. Unit tests for views are not working because they get stuck at the login page. Setting a breakpoint immediately after the view's response is returned and using
print response.content
results in output that contains the phrase 'please login'
How do I setup a user so the testcase can login? I've tried using my username and password, but it doesn't work in the unit test context.
The following code inserted at the beginning of the testcase creates a user, logs them in, and allows the rest of the test to contiue
self.user = User.objects.create_user(username='testuser', password='12345')
login = self.client.login(username='testuser', password='12345')
I would like to test a Django project:
template news.html:
{% if user.is_authentication %}
<a href=#>Logout ({{user.username}})</a>
{% else %}
<a href=#>Login</a>
{% endif %}
How do I create the situation that the user is already logged in (the accounts app, responsible for user login, has not implemented yet) such that the template will display the Logout link?
(a) Functional test:
def test_logged_in_user_can_see_logout_link(self):
# A logged-in user arrives at the main page
## How to mock a logged-in user here?
self.browser.get(self.live_server_url)
self.assertTrue(self.browser.find_element_by_link_text('Logout'))
(b) Unit test:
def test_logged_in_user_can_see_logout_link**(self):
# How to login the user here?
request = self.factory.get('/news/')
response = news(request)
self.assertContains(response, 'Logout')
In Django's base user, the property is_authenticated simply returns True in any case. On the AnonymousUser though it returns always False. The login_required decorator also checks for user.is_authenticated, by the way.
(b) Therefore, in the unit test scenario, you can easily pass a mocked object that also returns true for mocked_object.is_authenticated.
def test_logged_in_user_can_see_logout_link(self):
class MockUser:
is_authenticated = True
request = self.factory.get('/news/')
request.user = MockUser()
response = news(request)
self.assertContains(response, 'Logout')
(a) In the functional testing scenario however mocking is not so easy. I don't even think that it's recommended because these tests should actually test if all the parts that have already passed the unit tests work together flawlessly.
In the book "Obey The Testing Goat" by Harry J.W. Percival a session object is created before the first interaction between Selenium browser and site. The browser then uses that session cookie and is authenticated. It's a really nice trick.
I want to test if message is sent to user after submit. I'm using django.contrib.messages. Everything seems to be working during manual testing (runserver), but in unit test I don't get messages.
Code that stores message:
messages.success(request, _('Internationalized evil message.'))
Code that should test message:
from django.contrib.messages.api import get_messages
...
def test_message_should_sent_to_user(self):
"""After successful phone number submit, message should be displayed."""
response = self.client.post(
reverse('evil.views.evil_data_submit'), self.valid_data)
messages = get_messages(response.request)
self.assertNotEqual(len(messages), 0)
It looks like that no middleware is called during test client post method call.
Update after #Tisho answer
Messages should be found in response.context, even my guts say that it should work, but it doesn't. I've placed import pdb; pdb.set_trace() in django/contrib/messages/context_processors.py to see if its called during test client.post, its not.
I've double checked TEMPLATE_CONTEXT_PROCESSORS, MIDDLEWARE_CLASSES and INSTALLED_APPS - probably tomorrow I'll discover that I missed something.
Important detail
Forgot to mention that in case of successful submit view returns HttpResponseRedirect therefore response.context is empty.
Solution
View returns redirect (which has no context data), to solve that we can pass follow=True to client.post method (method suggested by #Tisho).
During unit tests, the message could be found in
response = self.client.post(
reverse('evil.views.evil_data_submit'), self.valid_data)
messages = response.context['messages']
If your view returns a redirect, response.context will be empty unless you pass follow=True, like so:
response = self.client.post(
reverse('evil.views.evil_data_submit'),
self.valid_data,
follow=True)
The best way I found is by using mock
http://www.voidspace.org.uk/python/mock/patch.html#patch-methods-start-and-stop
class SimpleCommentTestDirect(TestCase):
def setUp(self):
self._patcher1 = patch('django.contrib.messages.error')
self.mock_error = self._patcher1.start()
def tearDown(self):
self._patcher1.stop()
def test_error_message(self):
self.client.get('/vote/direct/unknownapp/comment/1/up/')
self.assertEqual(self.mock_error.call_args[0][1], 'Wrong request. Model.')
BTW: There are also should be a way to get request by using mock. And by using request object get message from django.contrib.messages.get_messages
Someone has probably already developed a technique for relieving the tedium for the following idiomatic unit test:
GET a url with form data already populated
POST a revised form with one or more fields edited
Check response (profit!)
Step 2 is the most tedious, cycling through the form fields. Are there any time-saving hacks for testing Django forms?
[Update: I'm not testing Django forms handling. I'm verifying that my application produces correct responses when a user makes changes to a form. This is an application which processes clinical information, hence a lot of possible responses to test.]
It depends what you are trying to test. I would target your tests a bit more finely than it sounds like you are doing.
If the code you need to test is the form validation logic, then I would simply instantiate the form class directly in your tests, pass it various data dictionaries and call .is_valid(), check for the proper errors or lack thereof. No need to involve HTML or HTTP requests.
If it's view logic (which IMO should be minimized) that you are testing, you will probably want to use the test client, but you shouldn't need to do multi-stage tests or very many tests at this level. In testing view logic I wouldn't scrape HTML (that's testing templates), I'd use response.context to pull out the form object from the context.
If what you want to test is that the templates contain the proper HTML to make the form actually work (in order to catch errors like forgetting to include the management form for a formset in the template), I use WebTest and django-webtest, which parse your HTML and make it easy to fill in field values and submit the form like a browser would.
You can use response.context and form.initial to get the values you need to post:
update_url = reverse('myobject_update',args=(myobject.pk,))
# GET the form
r = self.client.get(update_url)
# retrieve form data as dict
form = r.context['form']
data = form.initial # form is unbound but contains data
# manipulate some data
data['field_to_be_changed'] = 'updated_value'
# POST to the form
r = self.client.post(update_url, data)
# retrieve again
r = self.client.get(update_url)
self.assertContains(r, 'updated_value') # or
self.assertEqual(r.context['form'].initial['field_to_be_changed'], 'updated_value')
django-webtest is perfect for such tests:
from django_webtest import WebTest
class MyTestCase(WebTest):
def test_my_view(self)
form = self.app.get('/my-url/').form
self.assertEqual(form['my_field_10'].value, 'initial value')
form['field_25'] = 'foo'
response = form.submit() # all form fields are submitted
In my opinion it is better than twill for django testing because it provides access to django internals so native django's response.context, response.templates, self.assertTemplateUsed and self.assertFormError API is supported.
On other hand it is better than native django test client because it has much more powerful and easy API.
I'm a bit biased ;) but I believe that django-webtest is now the best way to write django tests.
It's not clear but one guess is that you have tests like this.
class TestSomething( TestCase ):
fixtures = [ "..." ]
def test_field1_should_work( self ):
response= self.client.get( "url with form data already populated" )
form_data = func_to_get_field( response )
form_data['field1']= new value
response= self.client.post( "url", form_data )
self.assert()
def test_field2_should_work( self ):
response= self.client.get( "url with form data already populated" )
form_data = func_to_get_field( response )
form_data['fields']= new value
response= self.client.post( "url", form_data )
self.assert()
First, you're doing too much. Simplify.
class TestFormDefaults( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_get_should_provide_defaults( self ):
response= self.client.get( "url with form data already populated" )
self.assert(...)
The above proves that the defaults populate the forms.
class TestPost( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_field1_should_work( self ):
# No need to GET URL, TestFormDefaults proved that it workd.
form_data= { expected form content based on fixture and previous test }
form_data['field1']= new value
response= self.client.post( "url", form_data )
self.assert()
Don't waste time doing a "get" for each "post". You can prove -- separately -- that the GET operations work. Once you have that proof, simply do the POSTs.
If you POSTS are highly session-specific and stateful, you can still do a GET, but don't bother parsing the response. You can prove (separately) that it has exactly the right fields.
To optimize your resting, consider this.
class TestPost( TestCase ):
fixtures = [ "some", "known", "database" ]
def test_many_changes_should_work( self ):
changes = [
( 'field1', 'someValue', 'some expected response' ),
( 'field2', 'someValue' ),
...
]
for field, value, expected in changes:
self.client.get( "url" ) # doesn't matter what it responds, we've already proven that it works.
form_data= { expected form content based on fixture and previous test }
form_data[field]= value
response self.client.post( "url", form_data )
self.assertEquas( expected, who knows what )
The above will obviously work, but it makes the number of tests appear small.
Think carefully about why you need to unit-test this. Forms are part of the core Django functionality, and as such are very well covered by Django's own unit tests. If all you're doing is basic create/update, which from your question it sounds like is the case, I don't see any reason to write unit tests for that.
I don't see how or why you need unit tests for this. Sounds to me like you're testing for (and correcting) possible user input, which is covered very simply with Django's form validation (and model validation in 1.2)
I'd recommed you to take a look into acceptance testing level tools like robot test framework or letucce which are thought just for you want to do "I'm verifying that my application produces correct responses when a user makes changes to a form", that sounds more like acceptance (black-box) testing than unit-testing.
For instace, Robot let you to define your tests in tabular form, you define the workflow once and then you can define easily a bunch of tests that exercise the workflow with different data.
You are possibly looking for tools that do front end testing like twill or selenium
Both of these generate python code, that can be included within the django tests, so when you run tests, it opens the urls, posts the data and inspects whatever you want!
It should help you to see these tests written for selenium, for an open source reusable django app.