unittest.mock sustituye colaboradores durante una prueba y registra su uso. Sirve para relojes, gateways y clientes externos, pero demasiados mocks acoplan la suite a la implementación.
Aplicar patch donde se busca
Si servicio.py usa from gateway import enviar, modifica servicio.enviar, porque allí se resolverá la función:
from unittest.mock import patch
from app.servicio import procesar
@patch("app.servicio.enviar", autospec=True)
def test_procesar_envia_evento(enviar_mock) -> None:
enviar_mock.return_value = {"id": "evt-7"}
resultado = procesar({"pedido_id": 7})
assert resultado == "evt-7"
enviar_mock.assert_called_once_with({"pedido_id": 7})
autospec=True detecta argumentos incompatibles. Usa un context manager si el patch solo se necesita en una parte y deja que la limpieza restaure el original.
Retornos, errores y secuencias
return_value modela éxito; side_effect puede lanzar una excepción o entregar resultados sucesivos. Cubre solo errores que la aplicación trate. Un mock que acepta cualquier atributo puede ocultar errores, por lo que una spec suele ser más segura.
Mock, MagicMock y contratos
Mock crea atributos bajo demanda y registra llamadas. MagicMock también proporciona métodos especiales como __enter__, __iter__ y __len__. Úsalo cuando el colaborador participe realmente en un protocolo del lenguaje, no solo por comodidad. Métodos mágicos disponibles sin intención pueden permitir que pase una prueba con un objeto poco realista.
Pasa una clase o instancia a spec para limitar los atributos. spec_set es más estricto e impide asignar nombres desconocidos. create_autospec() construye un mock con la firma original:
from unittest.mock import create_autospec
class Gateway:
def cobrar(self, pedido_id: int, importe: int) -> str:
...
gateway = create_autospec(Gateway, instance=True, spec_set=True)
gateway.cobrar.return_value = "pago-42"
assert gateway.cobrar(42, 1990) == "pago-42"
gateway.cobrar.assert_called_once_with(42, 1990)
Esto detecta atributos mal escritos y muchos cambios de firma. No ejecuta validaciones internas ni hace cumplir los tipos anotados. Sigue siendo necesaria una prueba de integración para demostrar la comunicación con la implementación real.
Modelar resultados y fallos con side_effect
Una excepción en side_effect permite probar un camino de error sin causar un fallo externo real. Un iterable produce resultados sucesivos y finalmente StopIteration:
from unittest.mock import Mock
consultar = Mock(side_effect=[TimeoutError, {"estado": "ok"}])
try:
consultar()
except TimeoutError:
pass
assert consultar() == {"estado": "ok"}
assert consultar.call_count == 2
También puedes usar una función para calcular la respuesta según los argumentos. Debe ser pequeña. Si reproduce toda la dependencia, el test mantiene una segunda implementación que puede repetir el mismo error o divergir.
wraps=objeto_real registra llamadas y delega la ejecución. Es un spy parcial, no aislamiento. Los efectos reales continúan, así que no envuelvas por accidente clientes de red, escrituras o relojes no controlados.
Comprobar interacciones sin acoplar detalles
assert_called_once_with() expresa bien un contrato de interacción. Para varias llamadas, compara call_args_list con objetos call:
from unittest.mock import Mock, call
publicar = Mock()
publicar("pedido.creado", {"id": 1})
publicar("pedido.creado", {"id": 2})
assert publicar.call_args_list == [
call("pedido.creado", {"id": 1}),
call("pedido.creado", {"id": 2}),
]
Si el orden no es contractual, usa assert_has_calls(..., any_order=True) o una comparación independiente del orden. No verifiques cada llamada auxiliar solo porque sea observable. Un refactor que conserva el comportamiento debería mantener los tests verdes.
ANY representa un argumento variable, como un identificador generado. Limítalo a ese campo y comprueba por separado propiedades importantes mediante call_args. Aceptarlo todo elimina capacidad para detectar regresiones.
Elegir la duración del patch
Un decorador dura toda la función. Un context manager delimita claramente el cambio. patch.object() ayuda cuando ya tienes el objeto o clase. patch.dict() modifica temporalmente un mapping, incluido os.environ, y lo restaura:
import os
from unittest.mock import patch
with patch.dict(os.environ, {"MODO": "test"}, clear=False):
assert os.environ["MODO"] == "test"
Evita llamar start() sin garantizar limpieza. En unittest.TestCase, registra inmediatamente patcher.stop con addCleanup(). Un patch que llega a otra prueba crea fallos dependientes del orden.
La regla del namespace sigue siendo esencial. Si app.servicio importó enviar directamente, sustituir después gateway.enviar no cambia la referencia ya enlazada. Sigue el import cuando un patch aparentemente válido no funciona.
Colaboradores asíncronos
AsyncMock representa una función esperable. Las versiones actuales permiten que patch() lo elija automáticamente para funciones asíncronas:
from unittest.mock import AsyncMock
buscar = AsyncMock(return_value={"id": 7})
resultado = await buscar(7)
assert resultado == {"id": 7}
buscar.assert_awaited_once_with(7)
Usa assert_awaited*, no solo assert_called*, cuando la espera forma parte del comportamiento. Para un context manager asíncrono, configura __aenter__ y __aexit__ explícitamente.
Simular límites arquitectónicos estables
Los mocks funcionan mejor en fronteras: reloj, generador de identificadores, cola, gateway de pagos o cliente de API. Dentro del dominio, objetos reales y funciones puras suelen producir pruebas más claras. Un fake en memoria puede modelar un repositorio de manera coherente para varias operaciones.
No simules una biblioteca externa completa. Encapsúlala detrás de una interfaz pequeña controlada por la aplicación y simula esa interfaz. Conserva algunos tests de integración para serialización, autenticación, timeouts y cambios de la API real. Este equilibrio evita una suite lenta y también una suite que solo confirma su configuración.
Revisar cada prueba
Pregunta si la dependencia necesita aislamiento, si el patch apunta al namespace de consulta y si una spec protege el contrato. Confirma que se verifica un resultado o efecto observable, que los fallos modelados son tratados por la aplicación y que el estado siempre se restaura. Finalmente, prueba la frontera real en una capa de integración adecuada.
Configurar propiedades y cadenas con moderación
Una propiedad necesita PropertyMock unido al tipo del mock, no a su instancia. Es posible configurar cadenas como cliente.sesion().enviar.return_value, pero una cadena larga suele revelar una interfaz incómoda. Crea un adaptador pequeño en lugar de enseñar a todos los tests la estructura interna de una biblioteca.
Cuando la secuencia encadenada sea realmente contractual, mock_calls y call.call_list() permiten representarla. Si basta el resultado final, una comprobación de estado suele resistir mejor los refactors.
Usar sentinelas para identidad
sentinel crea objetos únicos y legibles cuando el valor concreto no importa pero debe conservarse la identidad:
from unittest.mock import Mock, sentinel
guardar = Mock()
guardar(sentinel.conexion)
guardar.assert_called_once_with(sentinel.conexion)
Los mocks conservan referencias a argumentos mutables. Si el código modifica una lista después de la llamada, call_args mostrará el valor posterior. Cuando importa el estado durante la llamada, copia el argumento en un side_effect o mejora la frontera para recibir un valor inmutable.
Reset no equivale a aislamiento
reset_mock() borra el historial y normalmente conserva return_value y side_effect. Puede servir en una prueba con fases explícitas, pero compartir el mock entre tests crea estado oculto. Prefiere una instancia nueva y fixtures de alcance corto.
Evita también comprobaciones negativas demasiado amplias. assert_not_called() solo demuestra que ese mock no recibió llamadas. No prueba que faltaron todos los efectos externos, especialmente si se aplicó patch al namespace equivocado. Combínalo con un resultado observable y una spec.
Diferenciar dobles de prueba
Un stub devuelve respuestas preparadas, un spy observa llamadas, un fake proporciona una implementación simplificada y un mock, en sentido estricto, verifica expectativas de interacción. unittest.mock puede construir varios de ellos. Nombrar el papel ayuda a elegir.
Usa valores reales para reglas puras. Un fake en memoria puede representar un repositorio coherente. Un mock con spec encaja para confirmar la publicación obligatoria de un evento. Para una API externa, combina el adaptador simulado con tests de contrato o integración.
Antes de simular, considera una función pura o un fake pequeño. La guía de fixtures y monkeypatch de pytest presenta alternativas, mientras RESPX resulta más expresivo para HTTPX.
La documentación oficial de unittest.mock, consultada el 28 de julio de 2026, cubre patch, specs, llamadas y mocks asíncronos. Prioriza resultados y efectos observables; verifica llamadas internas solo si forman parte del contrato.