How can I run Selenium tests against a test database? - django

I like to run tests before I commit.
I'm new to Selenium and I don't understand how to run the tests and not change the database.
My local database has dozens of identical posted questions.
Is there any way that I can have have these tests run and not have the database restored to it's original state on tearDown?
from selenium import webdriver
from django.utils import unittest
from selenium.webdriver.support.ui import WebDriverWait
class TestAuthentication(unittest.TestCase):
scheme = 'http'
host = 'localhost'
port = '4444'
def setUp(self):
self._driver = webdriver.Firefox()
self._driver.implicitly_wait(5)
def login_as_Bryan(self):
self._driver.get('http://localhost:8000/account/signin/')
user = self._driver.find_element_by_id('id_username')
user.send_keys("Bryan")
password = self._driver.find_element_by_id('id_password')
password.send_keys('***************')
submit = self._driver.find_element_by_id('blogin')
submit.click()
def test_user_should_be_able_to_login_manually(self):
self.login_as_Bryan(self)
message = self._driver.find_element_by_class_name('darkred')
self.assertEqual("Welcome back Bryan, you are now logged in", message.text)
def test_Bryan_can_post_question(self):
self.login_as_Bryan()
self._driver.find_element_by_link_text("ask a question").click()
self._driver.find_element_by_id('id_title').send_keys("Question should succeed")
self._driver.find_element_by_id('editor').send_keys("This is the body text.")
self._driver.find_element_by_id('id_tags').send_keys("test")
self._driver.find_element_by_class_name("submit").click()
self.assertTrue(self._driver.find_element_by_link_text("Question should succeed"))
def tearDown(self):
self._driver.quit()

The issue is not so much Selenium as it is your execution environment. It depends on how you fire up your application.
In general, you need to bootstrap your application launch so that it points to a temporary database for use only during that test. After the test execution, you should delete that database.
Alternately, you can provide a UI mechanism in your actual website to clear / refresh the test database. In that case, you still need a test database, but you don't need to delete/recreate it with every test execution.

You can use django-selenium, it runs tests on test database

Related

Django testing - StaticLiveServerTestCase - client.cookies empty if there was another test case before

I'm testing my Django application with Selenium in Docker. I encounter a peculiar thing related to cookies availability (I use cookies to authenticate in my tests).
Here is the code that works:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from users.models import CustomUser
class SomeTest(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
cls.host = "web" # Docker service name
super().setUpClass()
CustomUser.objects.create_user(username="user", password="password")
def setUp(self):
self.browser = webdriver.Remote("http://selenium:4444/wd/hub", DesiredCapabilities.FIREFOX)
def tearDown(self):
self.browser.quit()
def test2(self):
self.client.login(username="user", password="password")
cookie = self.client.cookies["sessionid"]
...
However, when I insert there another test case before test2, let it be something as simple as
def test1(self):
pass
then the code crashes with the following error:
Traceback (most recent call last):
File "/home/mysite/functional_tests/test.py", line 28, in test2
cookie = self.client.cookies["sessionid"]
KeyError: 'sessionid'
So the only difference between the working and not-working code is a dummy test function, but what does it change? As far as I know the setUp and tearDown methods make sure that the "environment" is the same for every test case, no matter what happens in other test methods and here it clearly depends on the (non-)existence of other test cases before running my test... Is there something I misunderstand? Or is it some kind of a bug?
Any help will be appreciated.
My setup:
Django==2.2.5
selenium==3.141.0
Docker version - 19.03.5
I've solved it and I'm posting the answer here in case anyone else encounters similar issues.
So the problem here was not with test case order, Docker, Selenium, or anything within the code itself but with my lack of understanding of how class StaticLiveServerTestCase behaves. Namely, this class inherits from LiveServerTestCase which in turn inherits from TransactionTestCase which tears down the database after each test case (and sets it up before another test case) - more on this can be found in Django docs. And as I was creating the user in setUpClass - which is run once per all the test cases in the class - it was indeed created but removed (together with the whole database) after any first test case. Sowhen I was doing self.client.login(username="user", password="password") it was not a problem with cookies or authentication per se but with the fact that the user simply didn't exist.
:-)

Django + Selenium test hangs - deadlock?

I've been working in a Django project for a while and am starting on a new app, trying to do more automated testing with Selenium at the same time. I'm using http://www.tdd-django-tutorial.com/ as a guide.
I'm trying to test the ability to log in to my application. My test can pull up the page and fill in text fields without trouble, but when it clicks on the submit button, it hangs - Firefox keeps trying to load the new page but it never happens. It looks like deadlock to me, but I don't understand it well enough to know what's going on.
Other details: I'm using Django's built-in login view. I have another test that successfully logs in to the admin site. I can log in to my application just fine when I test manually. The application accesses a remote MySQL database.
Here's my test:
from django.test import TestCase, LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
class BucloudTest(LiveServerTestCase):
"""Tests shared functionality (login, network and app selection)."""
fixtures = ['24aug2012_dev_auth.json']
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(5)
def tearDown(self):
self.browser.quit()
def test_good_login(self):
"""Tests that a user can log in using valid credentials."""
self.browser.get(self.live_server_url + "/login/")
user_css = "[placeholder=Username]"
user_field = self.browser.find_element_by_css_selector(user_css)
user_field.send_keys("test_user1")
pw_css = "[placeholder=Password]"
pw_field = self.browser.find_element_by_css_selector(pw_css)
pw_field.send_keys("test")
button = self.browser.find_element_by_css_selector("[value='Sign in']")
button.click()
WebDriverWait(self.browser, 30).until(
lambda driver: driver.find_element_by_tag_name('body'))
body = self.browser.find_element_by_tag_name("body")
self.assertIn("Properties", body.text)
print "ran tests YAY!!"
I run the test with manage.py test functests --liveserver=localhost:8080-8090.
Thanks very much for any suggestions!
It might be due to the fact that the content of your test_good_login() function is not indented

How can Selenium see a record that I can't find from a debugger?

I am creating Selenium tests for my App.
I can create a new user, but I can't seem to figure out how to have it deleted from the database.
After the tests run successfully the first time, subsequent tests fail because the username already exists.
Why am I not able to query the newly created record in the debugger despite being able to see the new record on the page?
How do I delete a record from the database in a test?
This is what I have been doing:
from selenium import webdriver
from django.utils import unittest
from forum.models import Question, Answer, User
class TestOSQAAuthentication(unittest.TestCase):
scheme = 'http'
host = 'localhost'
port = '4444'
def setUp(self):
self._driver = webdriver.Firefox()
self._driver.implicitly_wait(25)
def test_anon_can_create_new_account_manually(self):
self._driver.get('http://localhost:8000/account/local/register/')
self._driver.find_element_by_id('id_username').send_keys('MrManual')
self._driver.find_element_by_id('id_email').send_keys('test#gmail.com')
self._driver.find_element_by_id('id_password1').send_keys('test')
self._driver.find_element_by_id('id_password2').send_keys('test')
self._driver.find_element_by_id('bnewaccount').click()
# verify MrManual was created
self._driver.get('http://localhost:8000/users/')
self._driver.find_element_by_link_text('MrManual')
# MrManual seems to be created, but I don't see MrManual in the database during debugging with:
# import ipdb; ipdb.set_trace()
#ipdb> User.objects.all()
#[<User: Bryan>, <User: Kallie>, <User: Stalin>]
# here I am trying to delete the user from the database directly.
User.objects.filter(username="MrManual").delete()
"""For some reason I can't delete the record from the database from the test.
Selenium can find the new user in the browser, but I can't query the database to find it."""
If you use Selenium, instead of django.utils.unittest.TestCase please use django.test.TransactionalTestCase or even better, LiveServerTestCase.

How do I get the user in Django test?

I have some external services. My Django app is built on top of my external service APIs. In order to talk to my external service, I have to pass in an auth cookies, which I can get by reading User (that cookie != django cookies).
Using test tools like webtests, requests, I have trouble writing my tests.
class MyTestCase(WebTest):
def test_my_view(self):
#client = Client()
#response = client.get(reverse('create')).form
form = self.app.get(reverse('create'), user='dummy').form
print form.fields.values()
form['name'] = 'omghell0'
print form
response = form.submit()
I need to submit a form, which creates, say, a user on my external service. But to do that, I normally would pass in request.user (in order to authenticate my privilege to external service). But I don't have request.user.
What options do I have for this kind of stuff?
Thanks...
Suppose this is my tests.py
import unittest
from django.test.client import Client
from django.core.urlresolvers import reverse
from django_webtest import WebTest
from django.contrib.auth.models import User
class SimpleTest(unittest.TestCase):
def setUp(self):
self.usr = User.objects.get(username='dummy')
print self.usr
.......
I get
Traceback (most recent call last):
File "/var/lib/graphyte-webclient/webclient/apps/codebundles/tests.py", line 10, in setUp
self.usr = User.objects.get(username='dummy')
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/query.py", line 341, in get
% self.model._meta.object_name)
DoesNotExist: User matching query does not exist
But if I test the User.objects in views, I am okay.
You need to use the setUp() method to create test users for testing - testing never uses live data, but creates a temporary test database to run your unit tests. Read this for more information: https://docs.djangoproject.com/en/dev/topics/testing/?from=olddocs#writing-unit-tests
EDIT:
Here's an example:
from django.utils import unittest
from django.contrib.auth.models import User
from myapp.models import ThisModel, ThatModel
class ModelTest(unittest.TestCase):
def setUp(self):
# Create some users
self.user_1 = User.objects.create_user('Chevy Chase', 'chevy#chase.com', 'chevyspassword')
self.user_2 = User.objects.create_user('Jim Carrey', 'jim#carrey.com', 'jimspassword')
self.user_3 = User.objects.create_user('Dennis Leary', 'dennis#leary.com', 'denisspassword')
Also note that, if you are going to use more than one method to test different functionality, you should use the tearDown method to destroy objects before reinstantiating them for the next test. This is something that took me a while to finally figure out, so I'll save you the trouble.
def tearDown(self):
# Clean up after each test
self.user_1.delete()
self.user_2.delete()
self.user_3.delete()
Django recommends using either unit tests or doc tests, as described here. You can put these tests into tests.py in each apps directory, and they will run when the command `python manage.py test" is used.
Django provides very helpful classes and functions for unit testing, as described here. In particular, the class django.test.Client is very convenient, and lets you control things like users.
https://docs.djangoproject.com/en/1.4/topics/testing/#module-django.test.client
Use the django test client to simulate requests. If you need to test the behavior of the returned result then use Selenium.

django unit tests without a db

Is there a possibility to write django unittests without setting up a db? I want to test business logic which doesn't require the db to set up. And while it is fast to setup a db, I really don't need it in some situations.
You can subclass DjangoTestSuiteRunner and override setup_databases and teardown_databases methods to pass.
Create a new settings file and set TEST_RUNNER to the new class you just created. Then when you're running your test, specify your new settings file with --settings flag.
Here is what I did:
Create a custom test suit runner similar to this:
from django.test.simple import DjangoTestSuiteRunner
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
Create a custom settings:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
When you're running your tests, run it like the following with --settings flag set to your new settings file:
python manage.py test myapp --settings='no_db_settings'
UPDATE: April/2018
Since Django 1.8, the module django.test.simple.DjangoTestSuiteRunner were moved to 'django.test.runner.DiscoverRunner'.
For more info check official doc section about custom test runners.
Generally tests in an application can be classified in to two categories
Unit tests, these test the individual snippets of code in insolation and do not require to go to the database
Integration test cases which actually go to the database and test the fully integrated logic.
Django supports both unit and integration tests.
Unit tests, do not require to setup and tear down database and these we should inherit from SimpleTestCase.
from django.test import SimpleTestCase
class ExampleUnitTest(SimpleTestCase):
def test_something_works(self):
self.assertTrue(True)
For integration test cases inherit from TestCase in turn inherits from TransactionTestCase and it will setup and tear down the database before running each test.
from django.test import TestCase
class ExampleIntegrationTest(TestCase):
def test_something_works(self):
#do something with database
self.assertTrue(True)
This strategy will ensure that database in created and destroyed only for the test cases that access the database and therefore tests will be more efficient
From django.test.simple
warnings.warn(
"The django.test.simple module and DjangoTestSuiteRunner are deprecated; "
"use django.test.runner.DiscoverRunner instead.",
RemovedInDjango18Warning)
So override DiscoverRunner instead of DjangoTestSuiteRunner.
from django.test.runner import DiscoverRunner
class NoDbTestRunner(DiscoverRunner):
""" A test runner to test without database creation/deletion """
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
Use like that :
python manage.py test --testrunner=app.filename.NoDbTestRunner app
I chose to inherit from django.test.runner.DiscoverRunner and make a couple of additions to the run_tests method.
My first addition checks to see if setting up a db is necessary and allows the normal setup_databases functionality to kick in if a db is necessary. My second addition allows the normal teardown_databases to run if the setup_databases method was allowed to run.
My code assumes that any TestCase that inherits from django.test.TransactionTestCase (and thus django.test.TestCase) requires a database to be setup. I made this assumption because the Django docs say:
If you need any of the other more complex and heavyweight Django-specific features like ... Testing or using the ORM ... then you should use TransactionTestCase or TestCase instead.
https://docs.djangoproject.com/en/1.6/topics/testing/tools/#django.test.SimpleTestCase
mysite/scripts/settings.py
from django.test import TransactionTestCase
from django.test.runner import DiscoverRunner
class MyDiscoverRunner(DiscoverRunner):
def run_tests(self, test_labels, extra_tests=None, **kwargs):
"""
Run the unit tests for all the test labels in the provided list.
Test labels should be dotted Python paths to test modules, test
classes, or test methods.
A list of 'extra' tests may also be provided; these tests
will be added to the test suite.
If any of the tests in the test suite inherit from
``django.test.TransactionTestCase``, databases will be setup.
Otherwise, databases will not be set up.
Returns the number of tests that failed.
"""
self.setup_test_environment()
suite = self.build_suite(test_labels, extra_tests)
# ----------------- First Addition --------------
need_databases = any(isinstance(test_case, TransactionTestCase)
for test_case in suite)
old_config = None
if need_databases:
# --------------- End First Addition ------------
old_config = self.setup_databases()
result = self.run_suite(suite)
# ----------------- Second Addition -------------
if need_databases:
# --------------- End Second Addition -----------
self.teardown_databases(old_config)
self.teardown_test_environment()
return self.suite_result(suite, result)
Finally, I added the following line to my project's settings.py file.
mysite/settings.py
TEST_RUNNER = 'mysite.scripts.settings.MyDiscoverRunner'
Now, when running only non-db-dependent tests, my test suite runs an order of magnitude faster! :)
Updated: also see this answer for using a third-party tool pytest.
#Cesar is right. After accidentally running ./manage.py test --settings=no_db_settings, without specifying an app name, my development database was wiped out.
For a safer manner, use the same NoDbTestRunner, but in conjunction with the following mysite/no_db_settings.py:
from mysite.settings import *
# Test runner with no database creation
TEST_RUNNER = 'mysite.scripts.testrunner.NoDbTestRunner'
# Use an alternative database as a safeguard against accidents
DATABASES['default']['NAME'] = '_test_mysite_db'
You need to create a database called _test_mysite_db using an external database tool. Then run the following command to create the corresponding tables:
./manage.py syncdb --settings=mysite.no_db_settings
If you're using South, also run the following command:
./manage.py migrate --settings=mysite.no_db_settings
OK!
You can now run unit tests blazingly fast (and safe) by:
./manage.py test myapp --settings=mysite.no_db_settings
As an alternative to modifying your settings to make NoDbTestRunner "safe", here's a modified version of NoDbTestRunner that closes the current database connection and removes the connection information from settings and the connection object. Works for me, test it in your environment before relying on it :)
class NoDbTestRunner(DjangoTestSuiteRunner):
""" A test runner to test without database creation """
def __init__(self, *args, **kwargs):
# hide/disconnect databases to prevent tests that
# *do* require a database which accidentally get
# run from altering your data
from django.db import connections
from django.conf import settings
connections.databases = settings.DATABASES = {}
connections._connections['default'].close()
del connections._connections['default']
super(NoDbTestRunner,self).__init__(*args,**kwargs)
def setup_databases(self, **kwargs):
""" Override the database creation defined in parent class """
pass
def teardown_databases(self, old_config, **kwargs):
""" Override the database teardown defined in parent class """
pass
Another solution would be to have your test class simply inherit from unittest.TestCase instead of any of Django's test classes. The Django docs (https://docs.djangoproject.com/en/2.0/topics/testing/overview/#writing-tests) contain the following warning about this:
Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but if your tests interact with the database their behavior will vary based on the order that the test runner executes them. This can lead to unit tests that pass when run in isolation but fail when run in a suite.
However, if your test doesn't use the database, this warning needn't concern you and you can reap the benefits of not having to run each test case in a transaction.
The above solutions are fine too. But the following solution will also reduce the db creation time if there are more number of migrations.
During unit testing, running syncdb instead of running all the south migrations will be much faster.
SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb
instead
My web host only allows creating and dropping databases from their Web GUI, so I was getting a "Got an error creating the test database: Permission denied" error when trying to run python manage.py test.
I'd hoped to use the --keepdb option to django-admin.py but it doesn't seem to be supported any longer as of Django 1.7.
What I ended up doing was modifying the Django code in .../django/db/backends/creation.py, specifically the _create_test_db and _destroy_test_db functions.
For _create_test_db I commented out the cursor.execute("CREATE DATABASE ... line and replaced it with pass so the try block wouldn't be empty.
For _destroy_test_db I just commented out cursor.execute("DROP DATABASE - I didn't need to replace it with anything because there was already another command in the block (time.sleep(1)).
After that my tests ran fine - though I did set up a test_ version of my regular database separately.
This isn't a great solution of course, because it will break if Django is upgraded, but I had a local copy of Django due to using virtualenv so at least I have control over when/if I upgrade to a newer version.
Another solution not mentioned: this was easy for me to implement because I already have multiple settings files (for local / staging / production) that inherit from base.py . So unlike other people I did not have to overwrite DATABASES['default'], as DATABASES isn't set in base.py
SimpleTestCase still tried to connect to my test database and run migrations. When I made a config/settings/test.py file that didn't set DATABASES to anything, then my unit tests ran without it. It allowed me to use models that had foreign key and unique constraint fields. (Reverse foreign key lookup, which requires a db lookup, fails.)
(Django 2.0.6)
PS code snippets
PROJECT_ROOT_DIR/config/settings/test.py:
from .base import *
#other test settings
#DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': 'PROJECT_ROOT_DIR/db.sqlite3',
# }
#}
cli, run from PROJECT_ROOT_DIR:
./manage.py test path.to.app.test --settings config.settings.test
path/to/app/test.py:
from django.test import SimpleTestCase
from .models import *
#^assume models.py imports User and defines Classified and UpgradePrice
class TestCaseWorkingTest(SimpleTestCase):
def test_case_working(self):
self.assertTrue(True)
def test_models_ok(self):
obj = UpgradePrice(title='test',price=1.00)
self.assertEqual(obj.title,'test')
def test_more_complex_model(self):
user = User(username='testuser',email='hi#hey.com')
self.assertEqual(user.username,'testuser')
def test_foreign_key(self):
user = User(username='testuser',email='hi#hey.com')
ad = Classified(user=user,headline='headline',body='body')
self.assertEqual(ad.user.username,'testuser')
#fails with error:
def test_reverse_foreign_key(self):
user = User(username='testuser',email='hi#hey.com')
ad = Classified(user=user,headline='headline',body='body')
print(user.classified_set.first())
self.assertTrue(True) #throws exception and never gets here
When using the nose test runner (django-nose), you can do something like this:
my_project/lib/nodb_test_runner.py:
from django_nose import NoseTestSuiteRunner
class NoDbTestRunner(NoseTestSuiteRunner):
"""
A test runner to test without database creation/deletion
Used for integration tests
"""
def setup_databases(self, **kwargs):
pass
def teardown_databases(self, old_config, **kwargs):
pass
In your settings.py you can specify the test runner there, i.e.
TEST_RUNNER = 'lib.nodb_test_runner.NoDbTestRunner' . # Was 'django_nose.NoseTestSuiteRunner'
OR
I wanted it for running specific tests only, so I run it like so:
python manage.py test integration_tests/integration_* --noinput --testrunner=lib.nodb_test_runner.NoDbTestRunner
You can set databases to an empty list inside the normal TestCase from django.test.
from django.test import TestCase
class NoDbTestCase(TestCase):
databases = []