How to use test fixtures with other framework then unit tests - django

I'm using lettuce framework for testing and i would like to run tests with fresh database with some test fixtures loaded. similarly to unit tests run, when test fixtures are defined
is it possible?

Here is a code snippet that loads the fixtures that's mostly taken from the Django test case. You just need to make sure that "db" points to the correct db (the test db). I do this by just passing in a custom settings file. "db" here points to just an alias, not an actual connection. If you are only using one database (not counting the test db) you just set this to 'default'. So if you test has a class attribute of 'fixtures' it will load the fixtures with the same rules as the loaddata management command.
if getattr(self, 'multi_db', False):
databases = connections
else:
databases = [DEFAULT_DB_ALIAS]
for db in databases:
if hasattr(self, 'fixtures'):
# We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures,
**{'verbosity': 0, 'database': db})
You will need to
import from django.core.management import call_command
to make this work.

Related

SQLAlchemy doesn't let me set up Flask apps multiple times in a test fixture

I am writing a Flask application that uses SQLAlchemy for its database backend.
The Flask application is created with an app factory called create_app.
from flask import Flask
def create_app(config_filename = None):
app = Flask(__name__)
if config_filename is None:
app.config.from_pyfile('config.py', silent=True)
else:
app.config.from_mapping(config_filename)
from .model import db
db.init_app(app)
db.create_all(app=app)
return app
The database model consists of a single object called Document.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Document(db.Model):
id = db.Column(db.Integer, primary_key=True)
document_uri = db.Column(db.String, nullable=False, unique=True)
I am using pytest to do unit testing. I create a pytest fixture called app_with_documents that calls the application factory to create an application and adds some Document objects to the database before the test is run, then empties out the database after the unit test has completed.
import pytest
from model import Document, db
from myapplication import create_app
#pytest.fixture
def app():
config = {
'SQLALCHEMY_DATABASE_URI': f"sqlite:///:memory:",
'TESTING': True,
'SQLALCHEMY_TRACK_MODIFICATIONS': False
}
app = create_app(config)
yield app
with app.app_context():
db.drop_all()
#pytest.fixture
def app_with_documents(app):
with app.app_context():
document_1 = Document(document_uri='Document 1')
document_2 = Document(document_uri='Document 2')
document_3 = Document(document_uri='Document 3')
document_4 = Document(document_uri='Document 4')
db.session.add_all([document_1, document_2, document_3, document_4])
db.session.commit()
return app
I have multiple unit tests that use this fixture.
def test_unit_test_1(app_with_documents):
...
def test_unit_test_2(app_with_documents):
...
If I run a single unit test everything works. If I run more than one test, subsequent unit tests crash at the db.session.commit() line in the test fixture setup with "no such table: document".
def do_execute(self, cursor, statement, parameters, context=None):
> cursor.execute(statement, parameters)
E sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: document [SQL: 'INSERT INTO document (document_uri) VALUES (?)'] [parameters: ('Document 1',)] (Background on this error at: http://sqlalche.me/e/e3q8)
What I expect is that each unit test gets its own brand-new identical prepopulated database so that all the tests would succeed.
(This is an issue with the database tables, not the unit tests. I see the bug even if my unit tests consist of just pass.)
The fact that the error message mentions a missing table makes it look like the db.create_all(app=app) in create_app is not being called after the first unit test runs. However, I have verified in the debugger that this application factory function is called once for every unit test as expected.
It is possible that my call to db.drop_all() is an incorrect way to clear out the database. So instead of an in-memory database, I tried creating one on disk and then deleting it as part of the test fixture cleanup. (This is the technique recommended in the Flask documentation.)
#pytest.fixture
def app():
db_fd, db_filename = tempfile.mkstemp(suffix='.sqlite')
config = {
'SQLALCHEMY_DATABASE_URI': f"sqlite:///{db_filename}",
'TESTING': True,
'SQLALCHEMY_TRACK_MODIFICATIONS': False
}
yield create_app(config)
os.close(db_fd)
os.unlink(db_filename)
This produces the same error.
Is this a bug in Flask and/or SQLAlchemy?
What is the correct way to write Flask test fixtures that prepopulate an application's database?
This is Flask 1.0.2, Flask-SQLAlchemy 2.3.2, and pytest 3.6.0, which are all the current latest versions.
In my conftest.py I was importing the contents of model.py in my application like so.
from model import Document, db
I was running the unit tests in Pycharm using Pycharm's pytest runner. If instead I run tests from the command line with python -m pytest I see the following error
ModuleNotFoundError: No module named 'model'
ERROR: could not load /Users/wmcneill/src/FlaskRestPlus/test/conftest.py
I can get my tests running from the command line by fully-qualifying the import path in conftest.py.
from myapplication.model import Document, db
When I do this all the unit tests pass. They also pass when I run the unit tests from inside Pycharm.
So it appears that I had incorrectly written an import statement in my unit tests. However, when I ran those unit tests via Pycharm, instead of seeing an error message about the import, the scripts launched but then had weird SQL errors.
I still haven't figured out why I saw the strange SQL errors I did. Presumably something subtle about the way global state is being handled. But changing the import line fixes my problem.

What's the best way to load stored procedures into the Django unit test database?

I've got some Postgres stored procedures that my selenium tests will depend on. In development, I load them with a line in a script:
cat stored_procedures.sql | python manage.py dbshell
This doesn't work when unit testing, since a fresh database is created from scratch. How can I load stored procedures saved in a file into the test database before unit tests are run?
I think you have few ways to make this. In my opinion, the best solution - to add migration with your custom SQL. In future, you'll need that migration not only at development, but also at production stage. So, It would be not clear deploy procedure, if you'll store change to DB in few places.
Other way - just to add execution of your SQL to setUp method of testCase.
Additional migration
You should create a new empty migration ./manage.py makemigrations --empty myApp
Add your SQL code to operations list
operations = [
migrations.RunSQL('RAW SQL CODE')
]
Another solution to this is to create a management command that executes the required SQL Query and then just execute this command at the very beginning of your tests.
Below is my case:
Management command:
import os
from django.core.management import BaseCommand
from django.db import connection
from applications.cardo.utils import perform_query
from backend.settings import BASE_DIR
class Command(BaseCommand):
help = 'Loads all database scripts'
def handle(self, **options):
db_scripts_path = os.path.join(BASE_DIR, 'scripts', 'database_scripts')
utils_path = os.path.join(db_scripts_path, 'utils.sql')
with open(utils_path, mode='r') as f:
sql_query = f.read()
with connection.cursor() as cursor:
cursor.execute(sql_query)
Now you can just go to the terminal and type
python manage.py load_database_scripts
and the scripts will be loaded.
With a helper function like this one
def load():
with django_db_blocker.unblock():
call_command('load_database_scripts')
You just call this load function before the test suite runs.

Emulating an app with models in a django unittest

Im writing some code which retrieves info about installed apps, especially defined models, and then does stuff based on that information, but Im having some problems writing a clean, nice unittest. Is there a way to emulate or add an app in unittests without have to run manage.py startproject, manage.py startapp in my testsfolder to have a test app available for unittests?
Sure, try this on for size:
from django.conf import settings
from django.core.management import call_command
from django.test.testcases import TestCase
from django.db.models import loading
class AppTestCase(TestCase):
'''
Adds apps specified in `self.apps` to `INSTALLED_APPS` and
performs a `syncdb` at runtime.
'''
apps = ()
_source_installed_apps = ()
def _pre_setup(self):
super(AppTestCase, self)._pre_setup()
if self.apps:
self._source_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = settings.INSTALLED_APPS + self.apps
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
def _post_teardown(self):
super(AppTestCase, self)._post_teardown()
if self._source_installed_apps:
settings.INSTALLED_APPS = self._source_installed_apps
self._source_installed_apps = ()
loading.cache.loaded = False
Your test case would look something like this:
class SomeAppTestCase(AppTestCase):
apps = ('someapp',)
In case you were wondering why, I did an override of _pre_setup() and _post_teardown() so I don't have to bother with calling super() in setUp() and tearDown() in my final test case. Otherwise, this is what I pulled out of Django's test runner. I whipped it up and it worked, although I'm sure that, with closer inspection, you can further optimize it and even avoid calling syncdb every time if it won't conflict with future tests.
EDIT:
So I seem to have gotten out of my way, thinking you need to dynamically add new models. If you've created an app for testing purposes only, here's what you can do to have it discovered during your tests.
In your project directory, create a test.py file that will contain your test settings. It should look something like this:
from settings import *
# registers test app for discovery
INSTALLED_APPS += ('path.to.test.app',)
You can now run your tests with python manage.py test --settings=myproject.test and your app will be in the installed apps.

django - how to detect test environment (check / determine if tests are being run)

How can I detect whether a view is being called in a test environment (e.g., from manage.py test)?
#pseudo_code
def my_view(request):
if not request.is_secure() and not TEST_ENVIRONMENT:
return HttpResponseForbidden()
Put this in your settings.py:
import sys
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
This tests whether the second commandline argument (after ./manage.py) was test. Then you can access this variable from other modules, like so:
from django.conf import settings
if settings.TESTING:
...
There are good reasons to do this: suppose you're accessing some backend service, other than Django's models and DB connections. Then you might need to know when to call the production service vs. the test service.
Create your own TestSuiteRunner subclass and change a setting or do whatever else you need to for the rest of your application. You specify the test runner in your settings:
TEST_RUNNER = 'your.project.MyTestSuiteRunner'
In general, you don't want to do this, but it works if you absolutely need it.
from django.conf import settings
from django.test.simple import DjangoTestSuiteRunner
class MyTestSuiteRunner(DjangoTestSuiteRunner):
def __init__(self, *args, **kwargs):
settings.IM_IN_TEST_MODE = True
super(MyTestSuiteRunner, self).__init__(*args, **kwargs)
Just look at request.META['SERVER_NAME']
def my_view(request):
if request.META['SERVER_NAME'] == "testserver":
print "This is test environment!"
There's also a way to temporarily overwrite settings in a unit test in Django. This might be a easier/cleaner solution for certain cases.
You can do this inside a test:
with self.settings(MY_SETTING='my_value'):
# test code
Or add it as a decorator on the test method:
#override_settings(MY_SETTING='my_value')
def test_my_test(self):
# test code
You can also set the decorator for the whole test case class:
#override_settings(MY_SETTING='my_value')
class MyTestCase(TestCase):
# test methods
For more info check the Django docs: https://docs.djangoproject.com/en/1.11/topics/testing/tools/#django.test.override_settings
I think the best approach is to run your tests using their own settings file (i.e. settings/tests.py). That file can look like this (the first line imports settings from a local.py settings file):
from local import *
TEST_MODE = True
Then do ducktyping to check if you are in test mode.
try:
if settings.TEST_MODE:
print 'foo'
except AttributeError:
pass
If you are multiple settings file for different environment, all you need to do is to create one settings file for testing.
For instance, your setting files are:
your_project/
|_ settings/
|_ __init__.py
|_ base.py <-- your original settings
|_ testing.py <-- for testing only
In your testing.py, add a TESTING flag:
from .base import *
TESTING = True
In your application, you can access settings.TESTING to check if you're in testing environment.
To run tests, use:
python manage.py test --settings your_project.settings.testing
While there's no official way to see whether we're in a test environment, django actually leaves some clues for us.
By default Django’s test runner automatically redirects all Django-sent email to a dummy outbox. This is accomplished by replacing EMAIL_BACKEND in a function called setup_test_environment, which in turn is called by a method of DiscoverRunner. So, we can check whether settings.EMAIL_BACKEND is set to 'django.core.mail.backends.locmem.EmailBackend'. That mean we're in a test environment.
A less hacky solution would be following the devs lead by adding our own setting by subclassing DisoverRunner and then overriding setup_test_environment method.
Piggybacking off of #Tobia's answer, I think it is better implemented in settings.py like this:
import sys
try:
TESTING = 'test' == sys.argv[1]
except IndexError:
TESTING = False
This will prevent it from catching things like ./manage.py loaddata test.json or ./manage.py i_am_not_running_a_test
I wanted to exclude some data migrations from being run in tests, and came up with this solution on a Django 3.2 project:
class Migration(migrations.Migration):
def apply(self, project_state, schema_editor, collect_sql=False):
import inspect
if 'create_test_db' in [i.function for i in inspect.stack()]:
return project_state
else:
return super().apply(project_state, schema_editor, collect_sql=collect_sql)
I haven't seen this suggested elsewhere, and for my purposes it's pretty clean. Of course, it might break if Django changes the name of the create_test_db method (or the return value of the apply method) at some point in time, but modifying this to work should be reasonably simple, since it's likely that some method exists in the stack that doesn't exist during non-test migration runs.

Loading fixtures in django unit tests

I'm trying to start writing unit tests for django and I'm having some questions about fixtures:
I made a fixture of my whole project db (not certain application) and I want to load it for each test, because it looks like loading only the fixture for certain app won't be enough.
I'd like to have the fixture stored in /proj_folder/fixtures/proj_fixture.json.
I've set the FIXTURE_DIRS = ('/fixtures/',) in my settings.py.
Then in my testcase I'm trying
fixtures = ['proj_fixture.json']
but my fixtures don't load.
How can this be solved?
How to add the place for searching fixtures?
In general, is it ok to load the fixture for the whole test_db for each test in each app (if it's quite small)?
Thanks!
I've specified path relative to project root in the TestCase like so:
from django.test import TestCase
class MyTestCase(TestCase):
fixtures = ['/myapp/fixtures/dump.json',]
...
and it worked without using FIXTURE_DIRS
Good practice is using PROJECT_ROOT variable in your settings.py:
import os.path
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
FIXTURE_DIRS = (os.path.join(PROJECT_ROOT, 'fixtures'),)
Do you really have a folder /fixtures/ on your hard disk?
You probably intended to use:
FIXTURE_DIRS = ('/path/to/proj_folder/fixtures/',)
Instead of creating fixures folder and placing fixtures in them (in every app), a better and neater way to handle this would be to put all fixtures in one folder at the project level and load them.
from django.core.management import call_command
class TestMachin(TestCase):
def setUp(self):
# Load fixtures
call_command('loaddata', 'fixtures/myfixture', verbosity=0)
Invoking call_command is equivalent to running :
manage.py loaddata /path/to/fixtures
Saying you have a project named hello_django with api app.
Following are steps to create fixtures for it:
Optional step: create fixture file from database: python manage.py dumpdata --format=json > api/fixtures/testdata.json
Create test directory: api/tests
Create empty file __init__.py in api/tests
Create test file: test_fixtures.py
from django.test import TestCase
class FixturesTestCase(TestCase):
fixtures = ['api/api/fixtures/testdata.json']
def test_it(self):
# implement your test here
Run the test to load fixtures into the database: python manage.py test api.tests
I did this and I didn't have to give a path reference, the fixture file name was enough for me.
class SomeTest(TestCase):
fixtures = ('myfixture.json',)
You have two options, depending on whether you have a fixture, or you have a set of Python code to populate the data.
For fixtures, use cls.fixtures, like shown in an answer to this question,
class MyTestCase(django.test.TestCase):
fixtures = ['/myapp/fixtures/dump.json',]
For Python, use cls.setUpTestData:
class MyTestCase(django.test.TestCase):
#classmethod
def setUpTestData(cls):
cls.create_fixture() # create_fixture is a custom function
setUpTestData is called by the TestCase.setUpClass.
You can use both, in which case fixtures is loaded first because setUpTestData is called after loading the fixtures.
You need to import from django.test import TestCase and NOT from unittest import TestCase. That fixed the problem for me.
If you have overridden setUpClass method, make sure you call super().setUpClass() method as the first line in the method. The code to load fixtures is in TestCase class.