Skip to content

Commit 73a1205

Browse files
authored
Clean up README
1 parent fe5004e commit 73a1205

File tree

1 file changed

+108
-70
lines changed

1 file changed

+108
-70
lines changed

README.md

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,48 @@
11
# pytest-flask-sqlalchemy-transactions
22

3+
[![Build Status](https://travis-ci.org/jeancochrane/pytest-flask-sqlalchemy-transactions.svg?branch=master)](https://travis-ci.org/jeancochrane/pytest-flask-sqlalchemy-transactions) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/Django.svg)
4+
35
A [pytest](https://docs.pytest.org/en/latest/) plugin providing fixtures for running tests in
46
transactions using Flask and SQLAlchemy.
57

8+
## Contents
9+
10+
- [**Motivation**](#motivation)
11+
- [**Quick examples**](#quick-examples)
12+
- [**Usage**](#usage)
13+
- [Installation](#installation)
14+
- [From PyPi](#from-pypi)
15+
- [Development version](#development-version)
16+
- [Configuration](#configuration)
17+
- [Conftest setup](#conftest-setup)
18+
- [Test configuration (.ini or .cfg file)](#test-configuration-ini-or-cfg-file)
19+
- [`db-connection-string`](#db-connection-string)
20+
- [`mocked-engines`](#mocked-engines)
21+
- [`mocked-sessions`](#mocked-sessions)
22+
- [`mocked-sessionmakers`](#mocked-sessionmakers)
23+
- [Fixtures](#fixtures)
24+
- [`db_session`](#db_session)
25+
- [`db_engine`](#db_engine)
26+
- [`module_engine`](#module_engine)
27+
- [Using the `transactional` mark](#using-the-transactional-mark)
28+
- [**Development**](#development)
29+
- [Running the tests](#running-the-tests)
30+
- [Acknowledgements](#acknowledgements)
31+
- [Copyright](#copyright)
32+
633
## Motivation
734

835
Inspired by [Django's built-in support for transactional
936
tests](https://jeancochrane.com/blog/django-test-transactions), this plugin
1037
seeks to provide comprehensive, easy-to-use fixtures for wrapping tests in
11-
database transactions using Pytest, Flask, and SQLAlchemy.
12-
13-
In order to test database operations in the context of stateful web applications,
14-
developers must pay an inordinate amount of attention to the database changes incurred by their
15-
tests, since unanticipated changes can leak state between tests and inhibit
16-
test isolation. While some test engineers advocate avoiding database
17-
interactions entirely (see Harry Percival's ["Fast Tests, Slow Tests, and Hot
18-
Lava"](https://www.obeythetestinggoat.com/book/chapter_hot_lava.html) for more
19-
on this perspective), this approach is infeasible in data-intensive web applications,
20-
where much of the business logic involves state changes to a database. For
21-
these kinds of applications, a solution is needed that will allow the developer
22-
to trigger and verify database updates while still ensuring test isolation.
23-
24-
The goal of this plugin is to make testing stateful Flask applications easier
25-
by providing fixtures that permit the developer to **make arbitrary
26-
database updates with the confidence that any changes made during a test will
27-
roll back** once the test exits.
38+
database transactions using Pytest, Flask, and SQLAlchemy. We aim to make
39+
testing stateful Flask applications easier by providing fixtures that permit
40+
the developer to **make arbitrary database updates with the confidence that
41+
any changes made during a test will roll back** once the test exits.
2842

2943
## Quick examples
3044

31-
Use the `db_session` fixture to make **database updates that won't persist beyond
45+
Use the [`db_session` fixture](#db_session) to make **database updates that won't persist beyond
3246
the body of the test**:
3347

3448
```python
@@ -44,9 +58,9 @@ def test_transaction_doesnt_persist(db_session):
4458
assert row.name != 'testing'
4559
```
4660

47-
The `db_engine` fixture works the same way, but **copies the API of
61+
The [`db_engine` fixture](#db_engine) works the same way, but **copies the API of
4862
SQLAlchemy's [Engine
49-
object](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine)**.
63+
object](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine)**:
5064

5165
```python
5266
def test_a_transaction_using_engine(db_engine):
@@ -58,10 +72,10 @@ def test_transaction_doesnt_persist(db_engine):
5872
assert row_name != 'testing'
5973
```
6074

61-
Use configuration properties to **mock database connections in an app with fixtures that enforce
62-
nested transactions**, allowing any method from the codebase to run inside
63-
a test that imports a transactional fixture (`db_session` or `db_engine`) with
64-
the assurance that any database changes made will be rolled back at the end of the test:
75+
Use [configuration properties](#test-configuration-ini-or-cfg-file) to
76+
**mock database connections in an app and enforce nested transactions**,
77+
allowing any method from the codebase to run inside a test with the assurance
78+
that any database changes made will be rolled back at the end of the test:
6579

6680
```ini
6781
# tests/tox.ini
@@ -105,8 +119,8 @@ def test_transaction_doesnt_persist(db_session):
105119
assert row.name != 'testing'
106120
```
107121

108-
Use the **`@pytest.mark.transactional` mark to enforce that a test gets run
109-
inside a transaction**:
122+
Use the [`@pytest.mark.transactional` mark](#using-the-transactional-mark)
123+
to **enforce that a test gets run inside a transaction**:
110124

111125
```python
112126
from api.database import db
@@ -136,18 +150,23 @@ Install using pip:
136150
pip install pytest-flask-sqlalchemy-transactions
137151
```
138152

153+
Once installed, pytest will detect the plugin automatically during test collection.
154+
For basic background on using third-party plugins with pytest, see the [pytest
155+
documentation](https://docs.pytest.org/en/latest/plugins.html?highlight=plugins).
156+
139157
### Development version
140158

141159
Clone the repo from GitHub and switch into the new directory:
142160

143161
```
144162
git clone git@github.com:jeancochrane/pytest-flask-sqlalchemy-transactions.git
163+
cd pytest-flask-sqlalchemy-transactions
145164
```
146165

147-
You can install using pip if you'd like:
166+
You can install using pip:
148167

149168
```
150-
pip install -e .
169+
pip install .
151170
```
152171

153172
Or install the plugin dependencies manually:
@@ -161,18 +180,19 @@ pip install -r requirements/main.txt
161180
### Conftest setup
162181

163182
This plugin assumes that a fixture called `_db` has been
164-
defined in the root conftest file for your tests. As a fixture, `_db` should
165-
expose access to a valid Session object that can interact with your database,
166-
for example via the [`SQLAlchemy` initialization object](http://flask-sqlalchemy.pocoo.org/2.3/api/#flask_sqlalchemy.SQLAlchemy)
183+
defined in the root conftest file for your tests. The `_db` fixture should
184+
expose access to a valid [SQLAlchemy `Session` object](http://docs.sqlalchemy.org/en/latest/orm/session_api.html?highlight=session#sqlalchemy.orm.session.Session) that can interact with your database,
185+
for example via the [`SQLAlchemy` initialization class](http://flask-sqlalchemy.pocoo.org/2.3/api/#flask_sqlalchemy.SQLAlchemy)
167186
that configures Flask-SQLAlchemy.
168187

169188
The fixtures in this plugin depend on this `_db` fixture to access your
170-
database and create nested transactions to run tests in.
189+
database and create nested transactions to run tests in. **You must define
190+
this fixture in your `conftest.py` file for the plugin to work.**
171191

172192
An example setup that will produce a valid `_db` fixture could look like this
173193
(this example comes from the [test setup](./tests/_conftest.py#L25-L61) for this repo):
174194

175-
```
195+
```python
176196
@pytest.fixture(scope='session')
177197
def database(request):
178198
'''
@@ -231,13 +251,13 @@ def _db(database):
231251
### Test configuration (.ini or .cfg file)
232252

233253
This plugin requires that you set up a test configuration file with a few
234-
specific properties under the `[pytest]` section. For background on pytest
254+
specific properties under the `[pytest]` section. For basic background on pytest
235255
configuration, see the [pytest docs](https://docs.pytest.org/en/latest/customize.html#adding-default-options).
236256

237257
#### `db-connection-string`
238258

239259
The `db-connection-string` property allows the plugin to access a test
240-
database. **This property is required.**
260+
database. **This property is required.**
241261

242262
Example:
243263

@@ -250,9 +270,11 @@ db-connection-string=postgresql://postgres@localhost:5432/pytest_test
250270

251271
The `mocked-engines` property directs the plugin to [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)
252272
objects in your codebase, typically SQLAlchemy [Engine](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine)
253-
instances, replacing them with the `db_engine` fixture in tests where either
254-
of the transactional fixtures (`db_session` or `db_engine`) are listed as dependencies.
255-
Objects should be formatted as standard Python import paths, like `api.database.engine`.
273+
instances, replacing them with the [`db_engine` fixture](#db_engine) in tests where either
274+
of the transactional fixtures ([`db_session`](#db_session) or [`db_engine`](#db_engine))
275+
are listed as dependencies. The values for this property should be formatted as standard
276+
Python import paths, like `api.database.engine`.
277+
256278
This property is optional.
257279

258280
Example:
@@ -273,12 +295,12 @@ mocked-engines=api.database.engine api.database.second_engine
273295

274296
The `mocked-sessions` property directs the plugin to [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)
275297
objects in your codebase, typically SQLAlchemy [Session](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine)
276-
instances, replacing them with the `db_session` fixture in tests where either
277-
of the transactional fixtures (`db_session` or `db_engine`) are listed as dependencies.
278-
Objects should be formatted as standard Python import paths, like `api.database.db.session`.
279-
This property is optional.
298+
instances, replacing them with the [`db_session`](#db_session) fixture in tests where either
299+
of the transactional fixtures ([`db_session`](#db_session) or [`db_engine`](#db_engine)) are
300+
listed as dependencies. Values for this property should be formatted as standard Python import
301+
paths, like `api.database.db.session`.
280302

281-
To patch multiple objects at once, separate the paths with a whitespace.
303+
This property is optional.
282304

283305
Example:
284306

@@ -287,16 +309,23 @@ Example:
287309
mocked-sessions=api.database.db.session
288310
```
289311

312+
To patch multiple objects at once, separate the paths with a whitespace:
313+
314+
```ini
315+
[pytest]
316+
mocked-sessions=api.database.db.session api.database.second_db.session
317+
```
318+
290319
#### `mocked-sessionmakers`
291320

292321
The `mocked-sessionmakers` property directs the plugin to [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)
293322
objects in your codebase, typically SQLAlchemy [sessionmaker](http://docs.sqlalchemy.org/en/latest/orm/session_api.html?highlight=sessionmaker#sqlalchemy.orm.session.sessionmaker)
294-
factories, replacing them with a mocked class that will return the `db_session` fixture
295-
in tests where either of the transactional fixtures (`db_session` or `db_engine`)
296-
are listed as dependencies. Objects should be formatted as standard Python import paths,
297-
like `api.database.WorkerSessionmaker`. This property is optional.
323+
factories, replacing them with a mocked class that will return the [`db_session`](#db_session) fixture
324+
in tests where either of the transactional fixtures ([`db_session`](#db_session) or [`db_engine`](#db_engine))
325+
are listed as dependencies. Values for this property should be formatted as standard Python import paths,
326+
like `api.database.WorkerSessionmaker`.
298327

299-
To patch multiple objects at once, separate the paths with a whitespace.
328+
This property is optional.
300329

301330
Example:
302331

@@ -305,12 +334,19 @@ Example:
305334
mocked-sessionmakers=api.database.WorkerSessionmaker
306335
```
307336

337+
To patch multiple objects at once, separate the paths with a whitespace.
338+
339+
```ini
340+
[pytest]
341+
mocked-sessionmakers=api.database.WorkerSessionmaker api.database.SecondWorkerSessionmaker
342+
```
343+
308344
## Fixtures
309345

310346
This plugin provides two fixtures for performing database updates inside nested
311-
transactions that get rolled back at the end of a test: `db_engine` and
312-
`db_session`. In addition, the plugin provides a fixture for direct database
313-
changes that will not get rolled back: `module_engine`. This fixture can be
347+
transactions that get rolled back at the end of a test: [`db_session`](#db_session) and
348+
[`db_engine`](#db_engine). In addition, the plugin provides a fixture for direct database
349+
changes that will not get rolled back: [`module_engine`](#module_engine). This fixture can be
314350
useful for setting up module- or session-scoped state.
315351

316352
### `db_session`
@@ -320,8 +356,8 @@ rolled back when the test exits. It exposes the same API as [SQLAlchemy's
320356
`scoped_session` object](http://docs.sqlalchemy.org/en/latest/orm/contextual.html#sqlalchemy.orm.scoping.scoped_session).
321357

322358
Listing this fixture as a dependency will activate any mocks that are specified
323-
by the configuration properties `mocked-engines`, `mocked-sessions`, or
324-
`mocked-sessionmakers` in a configuration file.
359+
by the configuration properties [`mocked-engines`](#mocked-engines), [`mocked-sessions`](#mocked-sessions),
360+
or [`mocked-sessionmakers`](#mocked-sessionmakers) in a configuration file.
325361

326362
Example:
327363

@@ -340,7 +376,7 @@ def test_transaction_doesnt_persist(db_session):
340376

341377
### `db_engine`
342378

343-
Like `db_session`, the `db_engine` fixture allows you to perform direct updates
379+
Like [`db_session`](#db_session), the `db_engine` fixture allows you to perform direct updates
344380
against the test database that will be rolled back when the test exits. It is
345381
an instance of Python's built-in [`MagicMock`](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.MagicMock)
346382
class, with a spec set to match the API of [SQLAlchemy's
@@ -357,8 +393,9 @@ methods of the `Engine` API can be called, but they will not perform any useful
357393
work.
358394

359395
Listing this fixture as a dependency will activate any mocks that are specified
360-
by the configuration properties `mocked-engines`, `mocked-sessions`, or
361-
`mocked-sessionmakers` in a configuration file.
396+
by the configuration properties [`mocked-engines`](#mocked-engines),
397+
[`mocked-sessions`](#mocked-sessions), or [`mocked-sessionmakers`](#mocked-sessionmakers)
398+
in a configuration file.
362399

363400
Example:
364401

@@ -374,15 +411,15 @@ def test_transaction_doesnt_persist(db_engine):
374411

375412
### `module_engine`
376413

377-
In contrast to `db_session` and `db_engine`, the `module_engine` fixture does
378-
not wrap its test in a database transaction. Instead, this fixture returns a
379-
SQLAlchemy `Engine` object that can be used to set up persistent state in tests
380-
or fixtures. Its API is identical to the [SQLAlchemy `Engine`
381-
API](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine).
414+
In contrast to [`db_session`](#db_session) and [`db_engine`](#db_engine),
415+
the `module_engine` fixture does not wrap its test in a database transaction.
416+
Instead, this fixture returns a SQLAlchemy `Engine` object that can be used to
417+
set up persistent state in tests or fixtures. Its API is identical to the
418+
[SQLAlchemy `Engine` API](http://docs.sqlalchemy.org/en/latest/core/connections.html#sqlalchemy.engine.Engine).
382419

383420
Listing this fixture as a dependency **will not** activate any mocks that are specified
384-
by the configuration properties `mocked-engines`, `mocked-sessions`, or
385-
`mocked-sessionmakers` in a configuration file.
421+
by the configuration properties [`mocked-engines`](#mocked-engines), [`mocked-sessions`](#mocked-sessions),
422+
or [`mocked-sessionmakers`](#mocked-sessionmakers) in a configuration file.
386423

387424
Example:
388425

@@ -399,16 +436,17 @@ def test_module_engine_changes_persist(db_engine):
399436
## Using the `transactional` mark
400437

401438
If you want to enforce transactional context but you don't need to use either
402-
of the built-in transactional fixtures (`db_session` or `db_engine`), you can
403-
use the `**@pytest.mark.transactional**` decorator to mark that a test should
404-
be run inside a transaction.
439+
of the built-in transactional fixtures ([`db_session`](#db_session) or [`db_engine`](#db_engine)),
440+
you can use the **`@pytest.mark.transactional`** decorator to mark that a test should
441+
be run inside a transaction. For basic background on marks, see the [pytest
442+
documentation](https://docs.pytest.org/en/latest/mark.html).
405443

406444
Note that since this approach assumes that you'll be performing database
407445
updates using connections defined in your app, you **must** mock the
408-
appropriate connections using the configuration properties `mocked-sessions`,
409-
`mocked-engines`, or `mocked-sessionmakers`. The `transactional` mark is, in
410-
essence, a way of triggering these mocks, so the mocks themselves are
411-
important.
446+
appropriate connections using the configuration properties [`mocked-sessions`](#mocked-sessions),
447+
[`mocked-engines`](#mocked-engines), or [`mocked-sessionmakers`](#mocked-sessionmakers).
448+
The `transactional` mark is, in essence, a way of triggering these mocks, so the mocks
449+
themselves are necessary to create the transactional context that you expect.
412450

413451
Example:
414452

0 commit comments

Comments
 (0)