No forms exist in unit test with django-webtest - django

I want to write a test that will test change the password in the application. I use the django-allauth. For testing, I use django-WebTest.
When I run my code, I get the message:
FAILED (errors=1)
Destroying test database for alias 'default'...
mark#mariusz-K73E:~/myapp$ python manage.py test users
Creating test database for alias 'default'...
.E
======================================================================
ERROR: test_password_change_use_template (myapp.users.tests.ChangePasswordTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/mark/myapp/users/tests.py", line 16, in test_password_change_use_template
password_change.form['oldpassword'] = "test123"
File "/home/mark/.virtualenvs/urlop/local/lib/python2.7/site-packages/webtest/response.py", line 56, in form
"You used response.form, but no forms exist")
TypeError: You used response.form, but no forms exist
My code:
from django_webtest import WebTest
from django_dynamic_fixture import G
from users.models import User
from django.core.urlresolvers import reverse
class ChangePasswordTest(WebTest):
def setUp(self):
self.user = G(User)
def test_password_change_code(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
def test_password_change_use_template(self):
password_change = self.app.get(reverse('account_change_password'), user=self.user)
password_change.form['oldpassword'] = "test123"
password_change.form['password1'] = "test456"
password_change.form['password2'] = "test456"
password_change.form.submit()
self.assertRedirects(password_change, reverse('change_password'))

WebTest tests the rendered content. There is no form found in the HTML (maybe the form tag is incorrect or missing).
If it's working in your manual test. You can try to print the response to see what's different:
def test_password_change_use_template(self):
response = self.app.get(reverse('account_change_password'), user=self.user)
print response

Related

for loop in django unittest

I want to test register view in django project,
so I build some fake test cases(self.correct_samples)
after register successfully, it should redirect to home page which means the status code should be 302.
from django.test import TestCase
from django.urls.base import reverse
class RegisterTests(TestCase):
def setUp(self):
url = reverse('account:register')
self.response = self.client.get(url)
self.correct_samples = (
('testuser1#email.com', 'testuser', 'test112233', 'test112233'),
('fakeuser#email.com', 'fake123', 'fakeuser111', 'fakeuser111'),
('correct#email.com', 'Jack', 'myfavorite', 'myfavorite'),
('failemail', 'Jack', 'myfavorite', 'myfavorite'), # fail for purpose
)
def test_register_form(self):
for test_case in self.correct_samples:
email, username, password1, password2 = test_case
self.response = self.client.post(reverse('account:register'), data={
'email': email,
'username': username,
'password1': password1,
'password2': password2,
})
self.assertEqual(self.response.status_code, 302)
self.assertRedirects(
self.response, expected_url='/', status_code=302, target_status_code=200)
The fourth data in self.correct_samples which is ('failemail', 'Jack', 'myfavorite', 'myfavorite') should be a fail case.
but after python manage.py test. It passed.
(env) C:\Users\User\myblog>python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
C:\Users\User\myblog\env\lib\site-packages\whitenoise\base.py:115: UserWarning: No directory at: C:\Users\User\myblog\staticfiles\
warnings.warn(u"No directory at: {}".format(root))
.
----------------------------------------------------------------------
Ran 1 test in 0.195s
OK
Destroying test database for alias 'default'...
Here comes the tricky thing,
it failed after switching order from fourth to first.
from django.test import TestCase
from django.urls.base import reverse
class RegisterTests(TestCase):
def setUp(self):
...
self.correct_samples = (
('failemail', 'Jack', 'myfavorite', 'myfavorite'), # fail for purpose
('testuser1#email.com', 'testuser', 'test112233', 'test112233'),
('fakeuser#email.com', 'fake123', 'fakeuser111', 'fakeuser111'),
('correct#email.com', 'Jack', 'myfavorite', 'myfavorite'),
)
def test_register_form(self):
...
result:
(env) C:\Users\User\myblog>python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
C:\Users\User\myblog\env\lib\site-packages\whitenoise\base.py:115: UserWarning: No directory at: C:\Users\User\myblog\staticfiles\
warnings.warn(u"No directory at: {}".format(root))
F
======================================================================
FAIL: test_register_form (account.tests.RegisterTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\User\myblog\account\tests.py", line 34, in test_register_form
self.assertEqual(self.response.status_code, 302)
AssertionError: 200 != 302
----------------------------------------------------------------------
Ran 1 test in 0.026s
FAILED (failures=1)
Destroying test database for alias 'default'...
Why this happened?
I have searched for related keywords like unittest in forloop, multiple testcases in forloop.
but it seems no answers for it, or maybe search through other keywords?
or something I missed or misunderstood?
thanks for helping.
Problem has been solved!
According to this questions How do you generate dynamic (parameterized) unit tests in Python?
Solution: parameterized

Cannot Authenticate Test Requests with Both User Token and API Key in Django Rest Framework

My project requires two authentication methods for some endpoints:
A global API key using this app.
A user authentication token using the default one provided by DRF
All works fine when working with the API using Postman or an iOS app, but I couldn't make the authentication work in my tests. The user auth works fine, but the global key fails.
This is how the HTTP headers look in Postman:
X-Api-Key: KEY_VALUE
Authorization: Token TOKEN_VALUE
I tried to experiment with the code in the shell and authentication worked fine using the exact same code used in the test! Only in the tests it fails so I'm not really sure how to debug this further.
Edit:
You can see a complete project on github.
Test output:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test (app.tests.MyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".../authtest/app/tests.py", line 21, in test
self.assertEqual(response.status_code, status.HTTP_200_OK)
AssertionError: 403 != 200
----------------------------------------------------------------------
Ran 1 test in 0.112s
FAILED (failures=1)
Destroying test database for alias 'default'...
When I open the shell with python manage.py shell and copy and paste the test code:
>>> from rest_framework.test import (APITestCase, APIRequestFactory, force_authenticate)
>>> from rest_framework import status
>>> from accounts.models import User
>>> from app.views import MyView
>>> user = User.objects.create(email="test#test.com")
>>> user.set_password('1234')
>>> factory = APIRequestFactory()
>>> API_KEY = "KIkKSSz7.ziURxOZv8e66f28eMLYwPNs7eEhrNtYl"
>>> headers = {"HTTP_X_API_KEY": API_KEY}
>>> request = factory.get('/myview', **headers)
>>> force_authenticate(request, user=user)
>>> response = MyView.as_view()(request)
>>> response.status_code
200
Also making the request with postman works. Any idea what's going on here?
The problem is the hardcoded API_KEY you are passing in your test which is not a valid key resulting in status 403. I think the reason might be django uses a separate test database when running tests and djangorestframework-api-key uses APIKey model to generate api_key.
You can confirm this by:
Removing HasAPIKey from permission classes in MyView and running test again.
or by passing empty string as API_KEY in your test resulting in same
error you are getting now.
so what i might suggest is generating a new valid api_key and passing it instead of hardcoded key.
Here i am sharing an updated test file of how you can carry out your test
from rest_framework.test import (APITestCase, APIRequestFactory, force_authenticate)
from rest_framework import status
from accounts.models import User
from .views import MyView
from rest_framework_api_key.models import APIKey
class MyTests(APITestCase):
def test(self):
user = User.objects.create(email="test#test.com")
user.set_password('1234')
user.save()
factory = APIRequestFactory()
api_key, key = APIKey.objects.create_key(name="my-remote-service")
headers = {"HTTP_X_API_KEY": key}
request = factory.get('/myview', **headers)
force_authenticate(request, user=user)
response = MyView.as_view()(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)

Django Testing: URL mapping to the Class Based View

I'm new to Django testing so trying basic testing codes. But it is showing one error in second test class
Tests.py
from django.test import TestCase,Client
from .views import PostList
from django.urls import resolve
class SmokeTest2(TestCase):
def test_math(self):
self.assertEqual(1+1,2)
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
print({'found':found})
self.assertEqual(found.func(), PostList)
views.py
class PostList(ListView):
model = Post
template_name = 'home.html'
urls.py
urlpatterns = [
path('',views.PostList.as_view(),name ='list'),
]
When i am printing found its showing the o/p
{'found': ResolverMatch(func=blog.views.PostList, args=(), kwargs={}, url_name=list, app_names=[], namespaces=[])}
But still I am getting this error
(blog_env) PS D:\django\blog_env\mysite> python manage.py test
D:\django\blog_env\mysite
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
{'found': ResolverMatch(func=blog.views.PostList, args=(), kwargs={}, url_name=list, app_names=[], namespaces=[])}
E.
======================================================================
ERROR: test_root_url_resolves_to_home_page_view (blog.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "D:\django\blog_env\mysite\blog\tests.py", line 19, in test_root_url_resolves_to_home_page_view
self.assertEqual(found.func(), PostList)
TypeError: view() missing 1 required positional argument: 'request'
----------------------------------------------------------------------
Ran 2 tests in 0.069s
FAILED (errors=1)
Destroying test database for alias 'default'...
I was stung by this issue just now, ended up finding the solution in the documentation
class-based views need to be compared by name, as the functions generated by as_view() won't be equal due to different object ids, so the assertion should look like the below:
from django.test import TestCase
from django.urls import resolve
from .views import HomePageView
class HomePageViewViewTest(TestCase):
def test_resolve_to_home_page_view(self):
resolver = resolve('/')
self.assertEqual(resolver.func.__name__, HomePageView.as_view().__name__)
from django.urls import resolve, reverse
class HomePageViewViewTest(TestCase):
def test_resolve_to_home_page_view(self):
resolver = resolve('/')
self.assertEqual(resolver.func.view_class, HomePageView)
You can try this, it worked for me!
Since you are testing a Class based View, from the Traceback it can be seen that it's missing the request object. You can use the RequestFactory provided by the django.test package. Better read the following RequestFactory Documentation to get a good view of it. It will solve your problem.
from django.urls import resolve, reverse
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
response = self.client.get(resolve('/'))
response = self.client.get(reverse('your_app_name:list'))
self.assertEqual(response.status_code, 200)

reliably kill phantomjs launched in setUpClass if derived class' setUpClass fails

I wrote a SeleniumTestCase class that launches PhantomJS in its setUpClass and kills it in its tearDownClass. However, if a derived class' setUpClass raises an error, the PhantomJS process is left hanging because SeleniumTestCase.tearDownClass doesn't get called.
from django.test import LiveServerTestCase
import sys, signal, os
from selenium import webdriver
errorShots = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', "errorShots")
class SeleniumTestCase(LiveServerTestCase):
#classmethod
def setUpClass(cls):
"""
Launches PhantomJS
"""
super(SeleniumTestCase, cls).setUpClass()
cls.browser = webdriver.PhantomJS()
#classmethod
def tearDownClass(cls):
"""
Saves a screenshot if the test failed, and kills PhantomJS
"""
print 'Tearing down...'
if cls.browser:
if sys.exc_info()[0]:
try:
os.mkdir(errorShots)
except:
pass
errorShotPath = os.path.join(
errorShots,
"ERROR_phantomjs_%s_%s.png" % (cls._testMethodName, datetime.datetime.now().isoformat())
)
cls.browser.save_screenshot(errorShotPath)
print 'Saved screenshot to', errorShotPath
cls.browser.service.process.send_signal(signal.SIGTERM)
cls.browser.quit()
class SetUpClassTest(SeleniumTestCase):
#classmethod
def setUpClass(cls):
print 'Setting Up'
super(SetUpClassTest, cls).setUpClass()
raise Error('gotcha!')
def test1(self):
pass
Output (note that "Tearing Down" doesn't get printed)
$ ./manage.py test
Creating test database for alias 'default'...
Setting Up
E
======================================================================
ERROR: setUpClass (trucks.tests.SetUpClassTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/andy/leased-on/trucks/tests.py", line 1416, in setUpClass
raise Error('gotcha!')
NameError: global name 'Error' is not defined
----------------------------------------------------------------------
Ran 0 tests in 1.034s
FAILED (errors=1)
Destroying test database for alias 'default'...
How can I kill PhantomJS after a suite's setUpClass fails?
I know I could switch to using setUp and addCleanup, but I want to avoid relaunching PhantomJS (and logging back into my app with it) before every single test.
I decided to use setUpModule and tearDownModule to launch and kill PhantomJS. I put the screenshot-saving code in an addCleanup hook.
from django.test import LiveServerTestCase
from selenium import webdriver
import sys
import signal
import os
import unittest
errorShots = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', "errorShots")
browser = None
def setUpModule():
"""
Launches PhantomJS
"""
global browser
sys.stdout.write('Starting PhantomJS...')
sys.stdout.flush()
browser = webdriver.PhantomJS()
print 'done'
def tearDownModule():
"""
kills PhantomJS
"""
if browser:
sys.stdout.write('Killing PhantomJS...')
sys.stdout.flush()
browser.service.process.send_signal(signal.SIGTERM)
browser.quit()
print 'done'
class SeleniumTestCase(LiveServerTestCase):
def setUp(self):
self.addCleanup(self.cleanup)
def cleanup(self):
"""
Saves a screenshot if the test failed
"""
if sys.exc_info()[0]:
try:
os.mkdir(errorShots)
except:
pass
errorShotPath = os.path.join(
errorShots,
"ERROR_phantomjs_%s_%s.png" % (self._testMethodName, datetime.datetime.now().isoformat())
)
browser.save_screenshot(errorShotPath)
print '\nSaved screenshot to', errorShotPath

Why is Django-1.3 not finding my fixtures for a UnitTest?

I'm trying to use my fixtures in a UnitTest.
AddFavoritesTestCase(unittest.TestCase):
fixtures = ['/Users/Bryan/work/osqa/fixtures/fixture_questions.json']
def setUp(self):
self.factory = RequestFactory()
def testAdminCanFavorite(self):
user = User.objects.get(pk=3)
...
self.assertEqual(response.status_code, 200)
======================================================================
ERROR: testAdminCanFavorite (forum.tests.tests_building_stickyness.AddFavoritesTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/Bryan/work/osqa/forum/tests/tests_building_stickyness.py", line 18, in testAdminCanFavorite
user = User.objects.get(pk=3) # Kallie has admin
File "/usr/local/lib/python2.7/site-packages/Django-1.3-py2.7.egg/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/Users/Bryan/work/osqa/forum/models/base.py", line 64, in get
obj = super(CachedQuerySet, self).get(*args, **kwargs)
File "/usr/local/lib/python2.7/site-packages/Django-1.3-py2.7.egg/django/db/models/query.py", line 349, in get
% self.model._meta.object_name)
DoesNotExist: User matching query does not exist.
It seems the fixtures are not loading.
I've been able to use the fixtures to populate the database, but for some reason the fixtures aren't being found in the tests.
The path is correct but I can't figure out what's going wrong.
$ ls /Users/Bryan/work/osqa/fixtures/fixture_questions.json
/Users/Bryan/work/osqa/fixtures/fixture_questions.json
Running the test at a higher verbosity shows that the fixtures are not being found.
I'm running Django 1.3.
Import the TestCase from django.test;
Not: import unittest
Not: import django.utils.unittest
But: import django.test
Like this:
from django.test import TestCase
class test_something(TestCase):
fixtures = ['one.json', 'two.json']
...
https://docs.djangoproject.com/en/1.3/topics/testing/#django.test.TestCase
You don't pass the full path to the fixture, just the fixture name:
fixtures = ['fixture_questions.json']
As long as the fixture is in a fixtures directory within an app that's in INSTALLED_APPS, Django will find it.