Yeda AI Tips · #082

English

Mocks asíncronos: verifica el await, no la llamada

Llamado pero nunca esperado con await. Es la trampa de los tests asíncronos que pasa en verde. Cuando tu código llama a una función async pero omite el await, Python crea el objeto corrutina y lo descarta — el trabajo nunca se ejecuta. Y si esa función es un AsyncMock en tu test, assert_called_once() igual pasa, porque llamar es exactamente lo que ocurrió. El test queda verde; producción queda rota.

Por qué llamado ≠ esperado

Desde Python 3.8, unittest.mock incluye AsyncMock, que lleva dos registros separados:

Un await olvidado suma al primer registro y nunca toca el segundo. Directo de la documentación de la biblioteca estándar:

>>> mock = AsyncMock()
>>> coro = mock()          # called — but never awaited
>>> mock.called
True
>>> mock.assert_awaited()
AssertionError: Expected mock to have been awaited.

Así que assert_called_once() es la pregunta equivocada para una dependencia asíncrona. Pregunta "¿construiste la corrutina?" — no "¿se ejecutó el trabajo?"

El arreglo de una palabra

# Fragile: passes even if the await is missing
service.send_email.assert_called_once_with(user.id)

# Correct: fails loudly when the coroutine was never awaited
service.send_email.assert_awaited_once_with(user.id)

AsyncMock replica toda la familia de aserciones de llamada del lado del await, así que la migración es mecánica:

Aserción de llamadaEquivalente de await
assert_called()assert_awaited()
assert_called_once()assert_awaited_once()
assert_called_with(...)assert_awaited_with(...)
assert_called_once_with(...)assert_awaited_once_with(...)
assert_any_call(...)assert_any_await(...)
assert_not_called()assert_not_awaited()
call_count / call_args_listawait_count / await_args_list

Regla práctica: si el mock es un AsyncMock, cada called en tus aserciones debería decir awaited. Esto importa el doble cuando un asistente de IA escribe tus tests — los modelos entrenados con años de código de mocks síncronos usan assert_called_once por defecto, así que revisa los tests asíncronos generados buscando exactamente esta sustitución.

Por qué este bug adora el código generado por IA

Un await faltante es un diff de un token que compila en Python puro, corre sin excepción y solo aparece como un RuntimeWarning: coroutine '...' was never awaited — impreso después del hecho, no lanzado. Los asistentes también lo producen al convertir código síncrono a asíncrono, porque cada punto de llamada requiere una edición manual. Tus tests son la red; assert_awaited_once es lo que hace que la red de verdad atrape.

Trucos avanzados

Recursos

¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.

Habla con nosotros · Lee el blog · Read in English