Integration test with native SQLAlchemy - python

I have some trouble with integration test. I'm using python 3.5, SQLAlchemy 1.2.0b3, latest docker image of postgresql. So, I wrote test:
# tests/integration/usecases/test_users_usecase.py
class TestGetUsersUsecase(unittest.TestCase):
def setUp(self):
Base.metadata.reflect(_pg)
Base.metadata.drop_all(_pg)
Base.metadata.create_all(_pg)
self._session = sessionmaker(bind=_pg, autoflush=True, autocommit=False, expire_on_commit=True)
self.session = self._session()
self.session.add(User(id=1, username='user1'))
self.session.commit()
self.pg = PostgresService(session=self.session)
def test_get_user(self):
expected = User(id=1, username='user1')
boilerplates.get_user_usecase(storage_service=self.pg, id=1, expected=expected)
# tests/boilerplates/usecases/user_usecases.py
def get_user_usecase(storage_service, id, expected):
u = GetUser(storage_service=storage_service)
actual = u.apply(id=id)
assert expected == actual
In usecase I did next:
# usecases/users.py
class GetUser(object):
"""
Usecase for getting user from storage service by Id
"""
def __init__(self, storage_service):
self.storage_service = storage_service
def apply(self, id):
user = self.storage_service.get_user_by_id(id=id)
if user is None:
raise UserDoesNotExists('User with id=\'%s\' does not exists' % id)
return user
storage_service.get_user_by_id looks like:
# infrastructure/postgres.py (method of Postgres class)
def get_user_by_id(self, id):
from anna.domain.user import User
return self.session.query(User).filter(User.id == id).one_or_none()
And it does not work in my integration test. But if I add print(actual) in test before assert - all is OK. I thought that my test is bad, I try many variants and all does not works. Also I tried return generator from storage_service.get_user_by_id() and it also does not work. What I did wrong? It works good only if print() was called in test.

Related

Why is the DELETE statement using psycopg is not working?

I am working on a delete request. All of my functions are working, but especially this one is not. Actually, it runs, and using my insomnia looks like the row was deleted, but when I get all rows it is still there.
This belongs to my init.py from model folder:
class DatabaseConnector:
#classmethod
def get_conn_cur(cls):
cls.conn = psycopg2.connect(**configs)
cls.cur = cls.conn.cursor()
#classmethod
def commit_and_close(cls):
cls.conn.commit()
cls.cur.close()
cls.conn.close()
This belongs to my anime_model.py folder:
class Anime(DatabaseConnector):
def __init__(self, anime: string, released_date: string, seasons: int) -> None:
self.anime = anime.lower().title()
self.released_date = released_date
self.seasons = seasons
#classmethod
def remove_an_anime(cls, anime_id: int):
cls.get_conn_cur()
cls.create_a_table()
query = f"DELETE FROM animes WHERE id={anime_id} RETURNING *";
cls.cur.execute(query)
anime = cls.cur.fetchall()
cls.cur.close()
cls.conn.close()
return anime
It belongs to my controller folder, anime_controller.py file:
def delete_an_anime(anime_id):
anime_to_delete = Anime.remove_an_anime(anime_id)
if not anime_to_delete:
return {"error": f"id {anime_id} not found"}, HTTPStatus.NOT_FOUND
serialized_anime = dict(zip(anime_columns, anime_to_delete))
return serialized_anime, HTTPStatus.ACCEPTED

Pytest Mock AWS SecurityManager

my project has a file called config.py which has, among others, the following code:
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_A = 'name_of_secret_database_A'
def secret(self):
if self.value:
return get_secret(self.value)
return {}
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
I need to somehow mock get_secret in tests with pytest for all enum calls, for example Secret.DATABASE_A.secret ()
You can use monkeypatch to override the behaviour of get_secret(). I have made the get_secret() method a static method of the Secret class, but you can make it part of any module you want and import it as well. Just make sure you change in in the monkeypatch.setattr() call as well.
import pytest
from enum import Enum
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_B = 'name_of_secret_database_B'
def secret(self):
if self.value:
return Secret.get_secret(self.value)
return {}
#staticmethod
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
def test_secret_method(monkeypatch):
def get_secret(secret_name):
return "supersecret"
monkeypatch.setattr(Secret, "get_secret", get_secret)
s = Secret.DATABASE_A
assert s.secret() == "supersecret"
This returns into 1 passed test.
What is happening here is, that I created a function get_secret() in my test_secret_method as well, and then overwrite the Secret.get_secret() with that new method. Now, you can use the Secret class in your test_method and be sure what the 'get_secret()' method will return without actually running the original code.

Python doctest: how to testing a database insert or delete function?

The question is simple, the answer I dont know...
I'm newbie with testing and I have problems testing class for drive a sql3 database. What is the best way for test a class like this? Test the class or test the init function is not a problem, but the others? the test insert a test row?
import sqlite3
class DataBase:
def __init__(self):
self._database_path = 'data.sql'
self._conn = sqlite3.connect(self._database_path)
self._cursor = self._conn.cursor()
def get(self, sql):
# select
self._cursor.execute(sql)
dataset = []
for row in self._cursor:
dataset.append(row)
return dataset
def post(self, sql):
# insert
self._cursor.execute(sql)
self._conn.commit()
Thank you for all of you, thank you for all your answers!!
You can use the rollback function of the database.
Just replace self._conn.commit() with self._conn.rollback() and you can test the validity of your sql with no effects on the data.
If you need to test a series of actions (i.e: get data->modify data->insert new data->remove some data->get data again) you can remove all the _conn.commit() in your code, run the tests and finally call _conn.rollback().
Example:
import sqlite3
class DataBase:
def __init__(self):
self._database_path = 'data.sql'
self._conn = sqlite3.connect(self._database_path)
self._cursor = self._conn.cursor()
def get(self, sql):
# select
self._cursor.execute(sql)
dataset = []
for row in self._cursor:
dataset.append(row)
return dataset
def post(self, sql):
# insert
self._cursor.execute(sql)
def delete(self, sql):
# delete
self._cursor.execute(sql)
def rollback(self):
self._conn.rollback()
# You do your tests:
db = DataBase()
data = db.get('select name from table')
new_data = ['new' + name for name in data]
db.post('insert into table values {}'.format(','.join('({})'.format(d) for d in new_data)))
db.delete('delete from table where name = \'newMario\'')
check = bool(db.get('select name from table where name = \'newMario\''))
if check:
print('delete ok')
# You make everything as before the test:
db.rollback()
I think the CursorTests in official sqlite3 tests is a good example.
https://github.com/python/cpython/blob/master/Lib/sqlite3/test/dbapi.py#L187
You can write setUp and tearDown methods to set up and rollback the database.
from unittest import TestCase
class TestDataBase(TestCase):
def setUp(self):
self.db = DataBase()
def test_get(self):
pass # your code here
def test_post(self):
pass # your code here

Flask-SQLALchemy Unit Test Problems

I'm writing a web app by imitating The Flask Mega-Tutorial.
As I was trying to add some unit test cases to my code. I found that the test cases in the Tutorial had many duplicated codes.
Here is the code segments:
def test_avatar(self):
u = User(nickname='john', email='john#example.com')
avatar = u.avatar(128)
expected = 'http://www.gravatar.com/avatar/d4c74594d841139328695756648b6bd6'
...
def test_make_unique_nickname(self):
u = User(nickname='john', email='john#example.com')
db.session.add(u)
db.session.commit()
...
The problem is that every time I want to test a new case I have to repeat this process:
u = User(nickname='john', email='john#example.com')
db.session.add(u)
db.session.commit()
So, I moved this process out and made it like this:
import unittest
from config import basedir
from app import app, db
from app.models import User
u = User(nickname='john', email='john#example.com') # I put this out because some cases may want to use this stuff.
def _init_database():
db.session.add(u)
db.session.commit(u)
class TestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:')
self.app = app.test_client()
db.create_all()
_init_database() # init database every time
def tearDown(self):
db.session.remove()
db.drop_all()
def test_case_1(self):
self.assertTrue(User.query.count() == 1) # case 1
def test_case_2(self):
self.assertTrue(User.query.count() == 1) # case 1
if __name__ == '__main__':
unittest.main()
As you can see, two test cases are the same. However, only one case can pass. the other one will fail.
But if I move u = User(nickname='john', email='john#example.com') into _init_database():
def _init_database():
u = User(nickname='john', email='john#example.com')
db.session.add(u)
db.session.commit(u)
every thing is fine now.
I really don't know why! Could you help me?
The User instance is an object that is controlled by the ORM, so it is not a good idea to make it a global variable. That object not only contains your data but also includes database information. By making it global you are using it first on the database for the first test, then on the database for the second test.
A better approach is to create a new User instance for each test, and return it in your setUp() method:
def _init_database():
u = User(nickname='john', email='john#example.com')
db.session.add(u)
db.session.commit()
return u
Then you can attach this user to your test case and access it from your tests.

sqlalchemy transaction does not rollback

For my integration test, I custom wrote the base class from unittest.TestCase.
def initialize_sql(engine, dbsession):
dbsession.configure(bind=engine)
Base.metadata.bind = engine
Base.metadata.drop_all(engine) # ensure the database is clean!
Base.metadata.create_all(engine)
try:
populate(dbsession)
except IntegrityError:
transaction.abort()
class HeavyTestBaseCase(unittest.TestCase):
__TEST__ = False
test_ini = 'test.ini'
#classmethod
def setUpClass(cls):
TEST_ROOT = os.path.dirname(os.path.abspath(__file__))
settings = appconfig('config:' + os.path.join(TEST_ROOT, cls.test_ini))
cls.engine = engine_from_config(settings, prefix='sqlalchemy.')
print 'Creating the tables on the test database %s' % cls.engine
cls.dbsession = scoped_session(sessionmaker(
extension=ZopeTransactionExtension()))
config = Configurator(settings=settings)
initialize_sql(cls.engine, cls.dbsession)
def tearDown(self):
transaction.abort() # strange name for rollback ...
#classmethod
def tearDownClass(cls):
Base.metadata.drop_all(cls.engine)
Now here is the test case:
from mock import Mock, patch
from aurum.models import User
from aurum.user import register_user
class TestRegisterUserIntegration(HeavyTestBaseCase):
__TEST__ = True
#classmethod
def setUpClass(cls):
cls.uid = 'uid1234'
cls.username = 'user01'
cls.password = 'password01'
cls.masteru = 'masteru'
cls.masterp = 'masterp'
cls.gcs_patcher = patch('aurum.user.GCS', autospec=True)
cls.gcs = cls.gcs_patcher.start()
cls.gcs.return_value.register.return_value = cls.uid
super(TestRegisterUserIntegration, cls).setUpClass()
def test_register_user01_successful_return_useid_and_shared_key(self):
result = register_user(self.username, self.password, self.masteru, self.masterp)
self.assertEqual(result.keys(), ['user_id', 'shared_key'])
self.assertEqual(result['user_id'], self.uid)
def test_register_user01_successful_write_to_database_query_is_not_none(self):
register_user(self.username, self.password, self.masteru, self.masterp)
result = self.dbsession.query(User).filter_by(username=self.username).first()
self.assertTrue(result is not None)
But rollback didn't do anything. One of the tests will fail because of duplicating keys constraint, which means the commit didn't get drop.
In the actual code, before returning I wrote transaction.commit() to commit the changes.
Any idea what's going on? Thanks
Adapt #zzzek's advise, here's the traceback:
http://pastebin.com/K9fin7ZH
The actual code is about this:
def add_user(dbsession, username, password):
with transaction.manager:
user = User(...)
dbsession.add(user)

Categories

Resources