I'm writing functional tests to test API endpoints with pytest.
I want to achieve:
that at the end of each test DB transaction rolls back so that each test starts with the same data.
at the end of the test session, I want to delete the testing DB.
Bellow is my conftest.py file, and what is currently happening is that transactions aren't rolled back, and when it comes to the end of the session, it keeps hanging at drop_database on teardown, while it doesn't hang at the beginning when checking if DB exists (marked also in comments).
What do I need to do to make those rollbacks? And is there anything else I need to close, in order for that drop_database to take place?
Thank you!
import pytest
import sqlalchemy
from sqlalchemy_utils import database_exists, create_database, drop_database
from alembic.command import upgrade
from alembic.config import Config
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from api.views import dtm_api, diagrams_api, labels_api
from .data.dummy_data import import_dummy
ALEMBIC_CONFIG = 'migrations/alembic.ini'
#pytest.fixture(scope='session')
def app(request):
_app = Flask(__name__)
_app.config.from_object('api.config.TestingConfig')
_app.register_blueprint(diagrams_api.bp)
_app.register_blueprint(dtm_api.bp)
_app.register_blueprint(labels_api.bp)
ctx = _app.app_context()
ctx.push()
def teardown():
ctx.pop()
request.addfinalizer(teardown)
yield _app
#pytest.fixture(scope='session')
def test_client(app, request):
yield app.test_client()
#pytest.yield_fixture(scope='session')
def db(app, request):
engine = sqlalchemy.create_engine(app.config.get("SQLALCHEMY_DATABASE_URI"))
if database_exists(engine.url):
drop_database(engine.url) #here it doesn't hang
create_database(engine.url)
_db = SQLAlchemy()
_db.init_app(app)
_db.app = app
#Make migrations and add dummy data
Migrate(app, _db)
config = Config(ALEMBIC_CONFIG)
config.set_main_option("script_location", "migrations")
with app.app_context():
upgrade(config, 'head')
import_dummy(_db)
def teardown():
drop_database(engine.url) #here it hangs
engine.dispose()
request.addfinalizer(teardown)
yield _db
#pytest.fixture(scope='function', autouse=True)
def session(db, request):
connection = db.engine.connect()
options = dict(bind=connection, binds={}, autoflush=False, autocommit=False)
db.session = db.create_scoped_session(options=options)
def teardown():
db.session.rollback()
db.session.close()
connection.close()
request.addfinalizer(teardown)
yield db.session
Related
can anyone tell me, how to make a separate Database just for Flask admin and maybe flask security?
this is how i am working with my PostgreSQL database just to load some tables and perform CRUD:
import flask_admin
from flask import Flask
from sqlalchemy import create_engine
app =Flask(__name__)
engine = create_engine{'postgresql://name:password#localhost/tablesdatabase')
i wish to make a separate database for flask-admin :( this is what i am trying)
admin = flask_admin.Admin(app)
app.config['SQLALCHEMY_BINDS'] = {'admindb' : 'postgresql://name:password#localhost/adminedatabase'}
admin.add_view(ModelView) ? // how can i impelement this ? with a seperate datbase ?
There is no official support, but you can customize Flask-SQLalchemy session to use different connections, here the example for using master slave connections, you can easily add as many connections as you want
from functools import partial
from sqlalchemy import orm
from flask import current_app
from flask_sqlalchemy import SQLAlchemy, get_state
class RoutingSession(orm.Session):
def __init__(self, db, autocommit=False, autoflush=True, **options):
self.app = db.get_app()
self.db = db
self._bind_name = None
orm.Session.__init__(
self, autocommit=autocommit, autoflush=autoflush,
bind=db.engine,
binds=db.get_binds(self.app),
**options,
)
def get_bind(self, mapper=None, clause=None):
try:
state = get_state(self.app)
except (AssertionError, AttributeError, TypeError) as err:
current_app.logger.info(
'cant get configuration. default bind. Error:' + err)
return orm.Session.get_bind(self, mapper, clause)
# If there are no binds configured, use default SQLALCHEMY_DATABASE_URI
if not state or not self.app.config['SQLALCHEMY_BINDS']:
return orm.Session.get_bind(self, mapper, clause)
# if want to user exact bind
if self._bind_name:
return state.db.get_engine(self.app, bind=self._bind_name)
else:
# if no bind is used connect to default
return orm.Session.get_bind(self, mapper, clause)
def using_bind(self, name):
bind_session = RoutingSession(self.db)
vars(bind_session).update(vars(self))
bind_session._bind_name = name
return bind_session
class RouteSQLAlchemy(SQLAlchemy):
def __init__(self, *args, **kwargs):
SQLAlchemy.__init__(self, *args, **kwargs)
self.session.using_bind = lambda s: self.session().using_bind(s)
def create_scoped_session(self, options=None):
if options is None:
options = {}
scopefunc = options.pop('scopefunc', None)
return orm.scoped_session(
partial(RoutingSession, self, **options),
scopefunc=scopefunc,
)
Than the default session will be master, when you want to select from slave you can call it directly, here the examples:
In your app:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///master'
app.config['SQLALCHEMY_BINDS'] = {
'slave': 'postgresql:///slave'
}
db = RouteSQLAlchemy(app)
Select from master
session.query(User).filter_by(id=1).first()
Select from slave
session.using_bind('slave').query(User).filter_by(id=1).first()
I have a Django Application and I'm trying to create some selenium tests for it. The problem is that every time I run the test my database gets cleaned.
This is my test:
import time
from django.contrib.auth.models import User
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class CrewTest(StaticLiveServerTestCase):
def setUp(self):
self.selenium = webdriver.Chrome('chromedriver')
super(CrewTest, self).setUp()
self.user = User.objects.create_user(username='user',
email=None,
password='password')
def test_register(self):
selenium = self.selenium
selenium.get('http://127.0.0.1:8000/static/myapp.html')
name = selenium.find_element_by_id('username')
password = selenium.find_element_by_id('password')
submit = selenium.find_element_by_id('submit')
name.send_keys('user')
password.send_keys('password')
submit.send_keys(Keys.RETURN)
selenium.page_source
time.sleep(1)
assert 'Welcome user' in selenium.page_source
The test is successful, but my database is empty after I run it. Should I have a separate database for selenium tests? Or can I just disable cleaning the database after the test.
I'm trying to test the response code of a view, but I'm either getting a 301 or does not exist.
urls.py
...
url(r'^myview/(?P<view_id>.*)/$', view_myview.index, name='myview'),
...
Test code 1:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123')
self.assertEqual(response.status_code, 200)
The above code gives:
AssertionError: 301 != 200
Test code 2:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123/')
self.assertEqual(response.status_code, 200)
The above code gives:
Mymodel matching query does not exist.
All I want to do is simple testing of my views to ensure they aren't throwing an error code, but I can't seem to find the right way to do it and I've tried many, many suggestions from the internets. Is there a different way to pass in view_id? What if I also want to throw in some query parameters?
EDIT: Updating to show the workaround I've used to accomplish what I'm trying to do, as horrible as it may be. I found that using dumpdata and fixtures took FOREVER.
from django.test import TestCase
from django.test import Client
import os
from . import urls_to_test # just a simple list of strings
class SimpleTest(TestCase):
""" Simply test if views return status 200 """
def setUp(self):
self.client = Client()
print('Dumping production database...')
os.system("sudo mysqldump mydb > /tmp/mydb.sql")
print('Loading production data into test database...')
os.system("sudo mysql test_mydb < /tmp/mydb.sql")
os.system("sudo rm -rf /tmp/mydb.sql")
def test_details(self):
for u in urls_to_test.test_urls:
print('Testing {}'.format(u))
response = self.client.get(u)
self.assertEqual(response.status_code, 200)
print('{} URLs tested!'.format(len(urls_to_test.test_urls)))
The first one doesn't work because Django is redirecting to the version with a final slash.
The second one tells you exactly why it doesn't work: you haven't created an item with id 123 - or indeed any items at all - within the test.
Consider creating object before testing its existance:
import unittest
from django.test import Client
from app.models import YourModel
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.obj = YourModel.objects.create(*your object goes here*)
def test_details(self):
response = self.client.get('/myview/123/') # It may be not /123/. It depends on how you generate url for model
self.assertEqual(response.status_code, 200)
I'm writing a quick little test view that I can GET every few minutes to check that my application and database servers are online and functioning.
from django.http import HttpResponse, HttpResponseServerError
from my_app.models import Model
def test(request):
'''
Returns a HttpResponse to prove the application/db servers work
'''
if request.method == "GET":
#test database access
count = Model.objects.values_list('id', flat=True).count()
if count:
return HttpResponse("OK")
return HttpResponseServerError("Error")
Is there a more minimal inbuilt database query that I could do to test that the db server is working and takling to the app server, without having to do a fake one like I did above?
You can import django connections and check with your databases:
from django.db import connections
try:
cursor = connections['default'].cursor()
except OperationalError:
return HttpResponseServerError("Error")
else:
return HttpResponse("OK")
return HttpResponseServerError("Error")
Documentation here
I've created the tests folder, written my first test that should open a browser, point to a page and login, then go to home page.
Test run and fail, as expected, but I can't find out why.
browser should be available, pytest-selenium is installed by pip.
import pytest
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_homepage(self):
request = self.client.get('/new')
request.user = self.user
self.assertEqual(request.status_code, 200)
def test_login(self):
request = self.client.get('/per/login')
username_field = self.browser.find_element_by_name('username')
username_field.send_keys('peppa')
password_field = self.browser.find_element_by_name('password')
password_field.send_keys('pig')
password_field.send_keys(Keys.RETURN)
test_homepage()
> username_field = self.browser.find_element_by_name('username')
E AttributeError: 'CreaPageTest' object has no attribute 'browser'
tests/test_ore_app_views.py:27: AttributeError
what am I missing?
Any advice to examples of this kind of test is really appreciated.
You should configure self.browser inside setUp function. You are also missing an import for Keys. Code should be like this.
import pytest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from django.contrib.auth.models import Group, Permission, User
from django.test import TestCase, RequestFactory
class CreaPageTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.browser = webdriver.Firefox()
Also please refer to the docs, here http://selenium-python.readthedocs.org/getting-started.html