bag.sqlalchemy.testing module

Fake objects for unit testing code that uses SQLAlchemy.

Problem: SQLAlchemy is the main thing making our automated tests slow. In larger systems, hitting the database (even if SQLite in memory) leads to multiple-minute test suite runs, making TDD (Test First) impossible.

Mocking SQLAlchemy is impossibly hard to keep doing in numerous tests because the SQLAlchemy API is made of many objects and methods (session, query, filter, order_by, all, first, one etc.). It is bad to need to change the mocks every time you change an implementation detail!

Is there really no easy way to unit-test code that uses SQLAlchemy?

Come on, we are programmers! We can do this!

Solution 1: Create a fake session which can be populated with entities in the Arrange phase of the unit test, and then provides these entities to the code being tested. FakeSessionByType is a fake that does this – it only pays attention to the model class being queried and ignores all filters and order_bys and whatever else.

This solution was moderately successful, but what is annoying in it is that, unlike the real session, it does not populate entities with their IDs when it is flushed – neither does it take care of foreign keys.

Solution 2: A more ambitious FakeSession class did not work out and has been removed already.

Solution 3: As of 2016-05, I am sidestepping this as I try to implement Robert C. Martin’s Clean Architecture in Python, which forbids I/O in the center layers of the system. The only place in the system that can import and use the session is the Repository, which is dependency-injected into the service layer. This means the repository will contain one function per operation or query – thus it must be easy to mock. This makes the code more testable.

class bag.sqlalchemy.testing.BaseFakeQuery(sas, typs)[source]

Bases: object

Base class for Query objects. Look at the subclasses.

all()[source]
count()[source]
first()[source]

Return a matching entity, or None.

get(id)[source]
one()[source]

Ensure there is only one result and returns i, or raise.

class bag.sqlalchemy.testing.BaseFakeSession[source]

Bases: object

Base class for fake SQLAlchemy sessions. Look at the subclasses.

add(entity)[source]
delete(entity)[source]
flush()[source]
no_autoflush = <bag.sqlalchemy.testing.FakeNoAutoFlush object>
query(*typs)[source]
class bag.sqlalchemy.testing.FakeNoAutoFlush[source]

Bases: object

class bag.sqlalchemy.testing.FakeQueryByType(sas, typs)[source]

Bases: bag.sqlalchemy.testing.BaseFakeQuery

filter(*a, **kw)[source]
filter_by(*a, **kw)
join(*a, **kw)
order_by(*a, **kw)
class bag.sqlalchemy.testing.FakeSessionByType(*a, query_cls=None, **kw)[source]

Bases: bag.sqlalchemy.testing.BaseFakeSession

Mock session that returns query results based on the model type.

This mock session can be configured to return the results you want based on the model type being queried.

add_query_results(typs, results)[source]