16. Evitar errores comunes con imports, módulos y nombres parcheados

16.1 Objetivo del tema

Después de aprender a usar patch, conviene revisar los errores más frecuentes. Muchos problemas no vienen de Mock en sí, sino de no entender qué nombre está usando realmente el código bajo prueba.

En este tema veremos fallos típicos, señales para diagnosticarlos y formas más robustas de escribir las pruebas.

Objetivo práctico: reconocer y corregir errores habituales con patch, imports y nombres reemplazados.

16.2 Error 1: parchear donde se define, no donde se usa

Este es el error más común. Supongamos tienda/notificaciones.py:

from tienda.email import ServicioEmail


def enviar_bienvenida(usuario):
    servicio = ServicioEmail()
    servicio.enviar(usuario["email"], "Bienvenido")

La función usa el nombre local ServicioEmail dentro de tienda.notificaciones.

16.3 Patch incorrecto

Este patch no reemplaza el nombre usado por la función:

with patch("tienda.email.ServicioEmail") as ServicioEmailMock:
    enviar_bienvenida({"email": "ana@example.com"})

Puede que el código siga creando la clase real porque tienda.notificaciones ya importó la referencia.

16.4 Patch correcto

La ruta correcta es:

with patch("tienda.notificaciones.ServicioEmail") as ServicioEmailMock:
    enviar_bienvenida({"email": "ana@example.com"})

La regla es revisar el módulo bajo prueba y parchear el nombre que aparece allí.

16.5 Error 2: no considerar alias

Si el código usa un alias, el patch debe respetarlo:

import requests as http


def obtener_usuario(usuario_id):
    respuesta = http.get(f"https://api.example.com/usuarios/{usuario_id}")
    return respuesta.json()

El módulo usa el nombre http, no requests.

16.6 Patch correcto con alias

Si la función está en usuarios/api.py, el patch correcto es:

with patch("usuarios.api.http.get") as get_mock:
    get_mock.return_value.json.return_value = {"id": 1, "nombre": "Ana"}

    usuario = obtener_usuario(1)

assert usuario == {"id": 1, "nombre": "Ana"}

Parchear requests.get puede no afectar al alias que ya usa el módulo.

16.7 Error 3: configurar la clase en lugar de la instancia

Cuando parcheamos una clase, el mock de la clase representa el constructor. La instancia está en return_value.

Código:

def enviar_bienvenida(usuario):
    servicio = ServicioEmail()
    servicio.enviar(usuario["email"], "Bienvenido")

Error frecuente:

with patch("tienda.notificaciones.ServicioEmail") as ServicioEmailMock:
    ServicioEmailMock.enviar.return_value = True
    enviar_bienvenida(usuario)

enviar se llama sobre la instancia, no sobre la clase mockeada.

16.8 Configuración correcta de instancia

Debemos configurar ServicioEmailMock.return_value:

with patch("tienda.notificaciones.ServicioEmail") as ServicioEmailMock:
    instancia = ServicioEmailMock.return_value

    enviar_bienvenida({"email": "ana@example.com"})

instancia.enviar.assert_called_once_with("ana@example.com", "Bienvenido")

Este patrón se repite cada vez que el código instancia una clase parcheada.

16.9 Error 4: dejar que un Mock invente atributos

Mock crea atributos dinámicamente. Eso puede ocultar errores de tipeo:

servicio = Mock()
servicio.enviar_bienvendia.return_value = True

Si el método real se llama enviar_bienvenida, el typo puede pasar inadvertido en la configuración. Más adelante veremos spec, spec_set y autospec para evitar este problema.

16.10 Error 5: aserciones débiles

Una prueba con patch puede pasar aunque no verifique lo importante:

def test_enviar_bienvenida_debil():
    with patch("tienda.notificaciones.ServicioEmail"):
        enviar_bienvenida({"email": "ana@example.com"})

Esta prueba solo verifica que no se lanzó una excepción. No comprueba que se haya llamado al email con los datos correctos.

16.11 Aserción más útil

Una versión más fuerte:

def test_enviar_bienvenida_envia_email_correcto():
    with patch("tienda.notificaciones.ServicioEmail") as ServicioEmailMock:
        instancia = ServicioEmailMock.return_value

        enviar_bienvenida({"email": "ana@example.com"})

    instancia.enviar.assert_called_once_with("ana@example.com", "Bienvenido")

Ahora la prueba verifica la interacción que nos importa.

16.12 Error 6: patch demasiado amplio

Si un patch dura más de lo necesario, puede afectar otras pruebas. Por eso conviene usar context managers, decoradores o fixtures con yield, que restauran automáticamente el valor original.

Evita iniciar patches manuales sin asegurarte de llamar a stop.

patcher = patch("tienda.sesiones.uuid4")
uuid4_mock = patcher.start()

# Si olvidas patcher.stop(), otras pruebas pueden quedar afectadas.

16.13 Forma más segura

Usa with cuando sea posible:

with patch("tienda.sesiones.uuid4") as uuid4_mock:
    uuid4_mock.return_value = "ABC123"
    sesion = crear_sesion("USR-1")

Al salir del bloque, el patch se revierte automáticamente.

16.14 Error 7: patch oculto en fixtures confusas

Una fixture puede ser útil, pero si oculta demasiados patches, la prueba se vuelve difícil de leer:

@pytest.fixture
def entorno_mockeado():
    with patch("modulo.a"), patch("modulo.b"), patch("modulo.c"):
        yield

Quien lee la prueba no sabe fácilmente qué fue reemplazado ni con qué valores.

16.15 Fixture más explícita

Es mejor devolver los mocks importantes con nombres claros:

@pytest.fixture
def email_mock():
    with patch("tienda.notificaciones.ServicioEmail") as ServicioEmailMock:
        yield ServicioEmailMock.return_value

La prueba puede usar email_mock y verificar sus llamadas de forma directa.

16.16 Error 8: mezclar demasiados patches en una sola prueba

Si una prueba necesita muchos patches, puede ser señal de que la unidad bajo prueba hace demasiadas cosas o crea demasiadas dependencias internamente.

En ese caso, considera refactorizar hacia dependencias explícitas, separar responsabilidades o usar fakes para estado en vez de mocks para cada paso interno.

Muchos patches no siempre son un problema de la prueba; a veces muestran un problema de diseño.

16.17 Error 9: olvidar que patch cambia un nombre temporalmente

Al salir del bloque, el nombre vuelve a su valor original:

with patch("tienda.configuracion.MODO_DEBUG", new=True):
    assert mostrar_detalle_error() is True

assert mostrar_detalle_error() is False

Esto es correcto y esperado. Si una prueba espera que el cambio persista, está usando patch de forma equivocada.

16.18 Error 10: no verificar que el mock fue usado

Cuando dudas si el patch se aplicó correctamente, agrega una aserción sobre el mock:

uuid4_mock.assert_called_once_with()

Si esta aserción falla, es una señal clara de que el código no usó el nombre parcheado o no recorrió el camino esperado.

16.19 Lista de diagnóstico

Cuando un patch no funciona, revisa:

  • El import exacto en el módulo bajo prueba.
  • Si la dependencia se importó con alias.
  • Si estás parcheando una clase y configuraste su return_value.
  • Si el código realmente ejecuta el camino que usa esa dependencia.
  • Si el patch está activo durante la llamada al código probado.
  • Si una fixture oculta o pisa otra configuración.

16.20 Ejercicio práctico

Este código está en reportes/exportacion.py:

from reportes.pdf import GeneradorPDF


def exportar_reporte(datos):
    generador = GeneradorPDF()
    archivo = generador.crear(datos)
    return archivo

Escribe la prueba correcta para reemplazar GeneradorPDF y hacer que crear devuelva "reporte.pdf".

16.21 Solución posible del ejercicio

La clase debe parchearse donde se usa: reportes.exportacion.GeneradorPDF.

from unittest.mock import patch

from reportes.exportacion import exportar_reporte


def test_exportar_reporte():
    datos = {"ventas": 100}

    with patch("reportes.exportacion.GeneradorPDF") as GeneradorPDFMock:
        instancia = GeneradorPDFMock.return_value
        instancia.crear.return_value = "reporte.pdf"

        archivo = exportar_reporte(datos)

    assert archivo == "reporte.pdf"
    GeneradorPDFMock.assert_called_once_with()
    instancia.crear.assert_called_once_with(datos)

La instancia se configura mediante GeneradorPDFMock.return_value, porque el código llama a GeneradorPDF().

16.22 Conclusión

Los errores con patch suelen aparecer por rutas incorrectas, alias ignorados, clases configuradas en el nivel equivocado o patches demasiado ocultos. La solución es leer el import real, parchear donde se usa la dependencia y verificar que el mock fue utilizado.

En el próximo tema veremos autospec y spec_set, herramientas que ayudan a detectar interfaces incorrectas y errores de nombres.