Contenido del curso

side_effect en mock para simular fallos

Resumen

Cuando un servicio externo falla, tu aplicación no debería caer con él. Aprender a usar side effects en mock te permite simular esos fallos temporales y validar que tu código aguante distintos escenarios sin depender de APIs reales. Es una técnica clave si trabajas con pruebas unitarias en Python y necesitas testear integraciones que a veces funcionan y a veces no.

¿Qué es un side effect en mock y para qué sirve?

Un side effect es la capacidad de modificar el comportamiento de un mock para que responda distinto en cada llamada. A veces devuelve datos correctos, a veces lanza una excepción. Esa flexibilidad es justo lo que necesitas para probar código resiliente.

Piensa en una integración de pagos: ¿qué pasa si alguien intenta con una tarjeta inválida y luego con una válida? Con side effects puedes simular ambos escenarios en un solo test, sin tocar la API real.

¿Qué es un side effect en pruebas unitarias? Es una lista de comportamientos que un mock ejecuta en orden cada vez que se le llama. Puedes mezclar excepciones y respuestas exitosas para simular fallos intermitentes.

¿Cómo escribo un test que primero falle y luego funcione?

La idea es llamar al método dos veces dentro del mismo test. La primera llamada debe lanzar una excepción; la segunda debe devolver datos válidos. Para capturar el error usas with self.assertRaises apuntando a la excepción que lanza la librería que estás mockeando, en este caso una de requests.

Dentro del patch, asignas a mock_get.side_effect una lista con dos elementos:

  • El primer elemento es la excepción que quieres que se dispare en la primera llamada.
  • El segundo elemento es un objeto que simula la respuesta exitosa de la segunda llamada.

Así, una sola prueba cubre el caso de error y el caso de éxito sin necesidad de duplicar lógica.

¿Cómo configuro la excepción en el side effect?

Debes pasar la clase de la excepción, no una instancia. Si pasas el objeto instanciado, el test fallará porque assertRaises espera la clase para hacer la comparación. Importa requests y usa la excepción correspondiente del módulo, por ejemplo una excepción de conexión o timeout.

¿Cómo simulo una respuesta exitosa con MagicMock?

Aquí entra una herramienta poderosa: unittest.mock.MagicMock. Esta clase te permite construir un objeto falso al que le defines los atributos y métodos que necesites, sin escribir una clase real.

Para replicar la respuesta de requests.get necesitas dos cosas:

  • Un atributo status_code con valor 200.
  • Un método json que devuelva el diccionario con los datos de geolocalización.

El truco está en el método json. Como en tu código lo invocas con paréntesis (response.json()), no basta con asignarle un diccionario. Tienes que asignarle una lambda, que es una función anónima que devuelve el valor inmediatamente al ser llamada. Algo como json=lambda: {"city": "Miami", ...}.

¿Por qué uso lambda en lugar de asignar el diccionario directamente? Porque json es un método, no un atributo. La lambda convierte el valor en una función ejecutable que retorna el diccionario cuando el código la llama con paréntesis.

¿Qué ventaja tiene MagicMock frente a definir clases falsas?

No necesitas crear una clase dummy cada vez que pruebas un endpoint. MagicMock acepta cualquier parámetro que le pases en el constructor y lo expone como atributo del objeto. Eso reduce código repetitivo y hace los tests más legibles.

¿Cómo verifico que el test cubre ambos escenarios?

Una técnica útil para confirmar que tu test realmente está validando lo que crees es romperlo a propósito. Cambia un valor esperado, por ejemplo de Miami a San Francisco, corre el test y observa el error. Si solo falla por esa diferencia, significa que el resto del flujo, incluyendo la excepción de la primera llamada, se ejecutó correctamente.

Luego restauras el valor original, vuelves a correr el test y debe pasar en verde. Este ciclo te da certeza de que ambos casos, el de error y el de éxito, están bien cubiertos.

Para correr un test específico desde la terminal, usa la ruta completa con el nombre del archivo, la clase y el método de prueba. Por ejemplo: test_api_client.NombreClase.test_side_effect.

¿Qué habilidades técnicas se ponen en práctica aquí?

Este flujo combina varios conceptos que vale la pena tener claros:

  • Patch y mock_get: el decorador @patch reemplaza temporalmente requests.get por un mock que tú controlas.
  • side_effect como lista: te permite encadenar comportamientos en orden, uno por cada llamada al mock.
  • assertRaises: contexto que captura excepciones esperadas y hace fallar el test si no se lanzan.
  • MagicMock: construye objetos falsos con atributos y métodos arbitrarios sin definir clases.
  • lambda: función anónima que devuelve un valor inmediato, ideal para simular métodos como json().
  • status_code 200: indica una respuesta HTTP exitosa, parte del contrato que tu código espera de la API.

Como ejercicio, modifica los tests para validar qué pasa cuando alguien envía una IP inválida. Si la IP no es válida, el código debe devolver un error; si es válida, debe responder con los datos de geolocalización. ¿Cómo estructurarías tú ese caso con side effects? Cuéntame en los comentarios.