Replies: 7 comments 11 replies
-
That answers scope (although switching to global by default from function by default is a raw deal), but what if I want to test on uvloop? |
Beta Was this translation helpful? Give feedback.
-
Not really, see #591 |
Beta Was this translation helpful? Give feedback.
-
I opened a draft PR with a proof-of-concept for class-scoped loops (#620). It's probably not a good idea to change the semantics of the existing asyncio mark as my initial post intended. Firstly, the pytest docs describe markers as being intended for test functions. Applying a marker to a class or module simply applies that marker to all test functions. Internally, we could still use markers, but it may be a better idea to them, e.g. Secondly, changing the semantics of the existing asyncio mark will introduce more breaking changes,making it more difficult for users to migrate. |
Beta Was this translation helpful? Give feedback.
-
The proposal did not account for parametrized event loops which have been added in v0.18. @pytest.mark.asyncio_event_loop(
policy=[
asyncio.DefaultEventLoopPolicy,
uvloop.EventLoopPolicy(),
]
)
class TestClass:
... |
Beta Was this translation helpful? Give feedback.
-
We're using the auto mode and async fixtures that are session-scoped. Latest pytest-asyncio tells us that I tried to remove the warning by removing our session-scoped |
Beta Was this translation helpful? Give feedback.
-
Following up on this thread (assuming it's still open), with import asyncio
import os
from asyncio import AbstractEventLoop
from collections.abc import AsyncIterator, Iterator
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
import pytest
@pytest.fixture(scope="session")
def event_loop(request: pytest.FixtureRequest) -> Iterator[AbstractEventLoop]:
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="session")
def db_engine() -> AsyncEngine:
return create_async_engine("postgresql+asyncpg://postgres:postgres@localhost:45432/postgres")
@pytest.fixture(scope="session")
async def db_session(db_engine: AsyncEngine) -> AsyncIterator[AsyncSession]:
# create DB tables, etc once for all tests from SQLAlchemy `Base`
async with db_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# expose session
async with AsyncSession(bind=db_engine) as session:
yield session
@pytest.fixture
async def db(db_session: AsyncSession) -> AsyncIterator[AsyncSession]:
"""Create a transactional test database session.
https://docs.sqlalchemy.org/en/20/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites
"""
async with db_session.begin():
yield db_session
await db_session.rollback() Similar to what @freywaid and @ramnes have mentioned, the thought was to create a single, reusable database Regrettably, this pattern no longer works in |
Beta Was this translation helpful? Give feedback.
-
The deprecation has already happened. The fallout from this change should be tracked in issues rather than a discussion. Therefore, I'll close this dicsussion. |
Beta Was this translation helpful? Give feedback.
-
Motivation
pytest-asyncio provides an asyncio event loop via the event_loop fixture. The fixture is function-scoped by default. This is useful for unit testing as it ensures a high level of isolation between test cases. However, as soon as users want to write larger tests that span multiple test cases the scope of the event_loop fixture needs to be extended.
Extending the scope of the event_loop fixture is currently achieved by reimplementing the fixture with an appropriate scope. This approach leads to code duplication, because users are forced to implement the fixture body over and over again. Users are also known to modify the fixture implementation and extend it with custom setup or teardown code.
Several problems arise from this: For one, changes to the fixture implementation in pytest-asyncio would require all users to change their fixture implementations accordingly. For another, requiring users to implement their own event_loop fixture somewhat defeats the purpose of pytest-asyncio in the first place.
Proposed solution
Pytest nodes are discovered by a hierarchy of collectors. Pytest marks can be located at different levels of this hierarchy (see Marking whole classes or modules).
The proposed solution is to derive the scope of the asyncio event loop from the location of the
asyncio
mark. Consider the following examples.Strict mode
Test functions
Each function test_a and test_b have their own asyncio mark. Therefore, the event loop scope will be limited to each test function.
Test classes
When using test classes the scope of the event loop can be per-class or per-function, depending on the location of
the
asyncio
mark.The functions test_a and test_b each run in their own loop, because the asyncio marker is directly attached to them.
The functions test_a and test_b run in a common loop, because the asyncio marker is attached to the class.
Module-scoped loops
If the entire module has the
asyncio
mark all tests are run in a module-scoped event loop.Auto mode
Function scope
Class scope
Module scope
Session scope
Considered alternatives
Dynamic fixture scopes
pytest-dev/pytest#6101
Introducing dynamic fixture scope in pytest is more effort compared to the proposed solution as it introduces new features to pytest.
Beta Was this translation helpful? Give feedback.
All reactions