Standard Mocking Patterns for Native SQL query executions + Fast API with Async Calls #603
-
Hi community, Framework - Fastapi I would to understand how to mock the database layers with async methods with pytest-asyncio only. incase of executing direct sql queries, is it possible to mock the connection, cursor object and the mocked result sent for maximum unit test coverage. Method Calling async def get_session(self, user_id: str) -> list[csession]:
result = await self._db_service.run(SQL_SELECT_USER_SESSIONS, {'user_id': user_id})
return result Method Declaration async def run(self, sql: str, params: dict[str,any] = None) -> any:
async with self._connection_pool.connection() as connection:
async with connection.cursor() as cursor:
try:
await cursor.execute(sql, params)
try:
return await cursor.fetchall()
except ProgrammingError as e:
return cursor.statusmessage
except psycopg.Error as e:
raise HTTPException(500, str(e)) Thanks for the support |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
@seifertm review requested |
Beta Was this translation helpful? Give feedback.
-
My understanding is that you want to test calls inside your run method, for example that Pytest-asyncio merely offers setup and teardown of asyncio event loops. It doesn't offer anything that will make mocking easier. Nevertheless, I'll first try to answer your question specifically and then answer more broadly. Specific answerIt's possible to use mock.AsyncMock from the standard library to mock coroutines and the like. In your case, you'd have to create a Mock that returns a mocked connection when connection = Mock()
connection_factory = AsyncMock()
connection_factory.__aenter__.return_value = connection
# More nesting goes here
connection_pool = Mock()
connection_pool.connection.return_value = connection_factory If you set up this kind of mock structure, you can the do assertions on the mock objects. Broad answerPersonally, I try to avoid mocking third-party code as much as possible. By mocking psycopg connections and cursors etc., the developer has to construct a bunch of mock objects. These mock objects make assumptions on how their psycopg counterparts behave. Most of the time, those assumptions are incomplete and/or wrong. This generally results in (lots of) test code that passes, even though the code under test is wrong. That being said, I'd probably try to test the database interaction in a larger-than-unit test. For example, using Once these tests get annoyingly slow, you can optimize from there. For example, you can provide two different implementations for This gets easier if the user of Also: It's fine to mock This is my personal opinion on the matter. I'm aware that it's unpopular :) But in my experience it's highly effective. |
Beta Was this translation helpful? Give feedback.
My understanding is that you want to test calls inside your run method, for example that
cursor.execute
is called with the correctsql
argument.Pytest-asyncio merely offers setup and teardown of asyncio event loops. It doesn't offer anything that will make mocking easier. Nevertheless, I'll first try to answer your question specifically and then answer more broadly.
Specific answer
It's possible to use mock.AsyncMock from the standard library to mock coroutines and the like.
In your case, you'd have to create a Mock that returns a mocked connection when
__aenter__
is invoked (i.e. when theasync with
context is entered). The mock object returned by__aenter__
represents your connection v…