11import contextlib
22import json
3- from collections .abc import AsyncGenerator , AsyncIterator , Iterable , Iterator
3+ from collections .abc import AsyncIterator , Iterable , Iterator
44from pathlib import Path
55from typing import Any , NamedTuple
66
99import pytest
1010from _pytest .config import Config
1111from _pytest .nodes import Item
12+ from asgi_lifespan import LifespanManager
13+ from fastapi import FastAPI
1214from sqlalchemy import text
1315from sqlalchemy .ext .asyncio import AsyncConnection , AsyncEngine
1416
1517from database .setup import expdb_database , user_database
16- from main import create_api , lifespan
18+ from main import create_api
1719from routers .dependencies import expdb_connection , userdb_connection
1820
1921PHP_API_URL = "http://php-api:80/api/v1/json"
@@ -51,12 +53,6 @@ async def temporary_records(
5153 await connection .commit ()
5254
5355
54- @pytest .fixture (autouse = True , scope = "session" )
55- async def one_lifespan () -> AsyncGenerator [None , None ]:
56- async with lifespan (app = None ):
57- yield
58-
59-
6056@pytest .fixture
6157async def expdb_test () -> AsyncIterator [AsyncConnection ]:
6258 async with automatic_rollback (expdb_database ()) as connection :
@@ -69,20 +65,34 @@ async def user_test() -> AsyncIterator[AsyncConnection]:
6965 yield connection
7066
7167
72- @pytest .fixture
68+ # The PHP API fixture can be session scoped since they do not need access to
69+ # function-scoped database transactions.
70+ @pytest .fixture (scope = "session" )
7371async def php_api () -> AsyncIterator [httpx .AsyncClient ]:
7472 async with httpx .AsyncClient (base_url = PHP_API_URL ) as client :
7573 yield client
7674
7775
76+ @pytest .fixture (scope = "session" )
77+ async def app () -> AsyncIterator [FastAPI ]:
78+ _app = create_api (Path (__file__ ).parent / "config.test.toml" )
79+ async with LifespanManager (_app ):
80+ yield _app
81+
82+
7883@pytest .fixture
7984async def py_api (
80- expdb_test : AsyncConnection , user_test : AsyncConnection
85+ expdb_test : AsyncConnection , user_test : AsyncConnection , app : FastAPI
8186) -> AsyncIterator [httpx .AsyncClient ]:
82- app = create_api (Path (__file__ ).parent / "config.test.toml" )
87+ """Create test client which automatically rolls back database updates on teardown."""
88+ # Using the function-scoped database fixtures automatically benefits the
89+ # automatic rollbacks, but also lets a test author write to a database
90+ # transaction that is shared with the app. That is, it enables:
91+ #
92+ # def my_test(expdb_test, py_api):
93+ # expdb_test.execute(...) # write some data # noqa: ERA001
94+ # py_api.get(...) # read that data # noqa: ERA001
8395
84- # We use async generator functions because fixtures may not be called directly.
85- # The async generator returns the test connections for FastAPI to handle properly
8696 async def override_expdb () -> AsyncIterator [AsyncConnection ]:
8797 yield expdb_test
8898
@@ -91,15 +101,17 @@ async def override_userdb() -> AsyncIterator[AsyncConnection]:
91101
92102 app .dependency_overrides [expdb_connection ] = override_expdb
93103 app .dependency_overrides [userdb_connection ] = override_userdb
94- # We do not use the Lifespan manager for now because our auto-use fixture
95- # `one_lifespan` will do setup and teardown at a session scope level instead.
104+
96105 async with httpx .AsyncClient (
97106 transport = httpx .ASGITransport (app = app ),
98107 base_url = "http://test" ,
99108 follow_redirects = True ,
100109 ) as client :
101110 yield client
102111
112+ app .dependency_overrides [expdb_connection ] = expdb_connection
113+ app .dependency_overrides [userdb_connection ] = userdb_connection
114+
103115
104116@pytest .fixture
105117def dataset_130 () -> Iterator [dict [str , Any ]]:
0 commit comments