No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Uso de Side Effects en Mocking con Python

12/20
Recursos

Mock nos permite simular comportamientos variables, una herramienta útil cuando queremos probar cómo reacciona nuestro código ante diferentes escenarios sin modificar el entorno real. Uno de los usos más poderosos es el “side effect”, que nos ayuda a hacer que un método falle en un caso y funcione en otro. Esto es clave para manejar errores temporales, como en el caso de una API de pagos que rechaza una tarjeta incorrecta, pero acepta una correcta en un segundo intento.

¿Cómo se define un “side effect” en Mock?

El “side effect” en Mock nos permite modificar el comportamiento de un método en distintas llamadas. Se define como una lista de comportamientos, donde cada elemento de la lista corresponde al resultado de una llamada específica. Esto permite:

  • Simular fallos de manera controlada, como lanzar excepciones específicas en las pruebas.
  • Probar el código bajo diferentes condiciones sin interactuar con los servicios externos.

¿Cómo se maneja un error y una respuesta exitosa en pruebas unitarias?

En una prueba unitaria con Mock, podemos definir comportamientos variables. Por ejemplo, para simular una excepción, usamos la estructura side_effect, donde la primera llamada lanza un error y la segunda retorna una respuesta exitosa. Esto permite cubrir ambos casos sin necesidad de realizar un request real.

  • Definimos el primer comportamiento con una excepción.
  • Luego, para el segundo comportamiento, usamos Mock para devolver un objeto con los valores que esperamos, simulando una respuesta exitosa.

¿Qué métodos auxiliares facilita Mock?

Mock facilita métodos como raiseException, que lanza una excepción específica, y mock, que permite crear objetos personalizados. Estos objetos pueden tener parámetros configurables como status_code y devolver datos específicos al llamar métodos como JSON. Este tipo de pruebas es crucial para validar la resiliencia del software ante errores temporales.

¿Cómo integrar validaciones adicionales en las pruebas?

Para reforzar las pruebas, puedes agregar validaciones adicionales, como simular el envío de una IP inválida. En este caso, si la IP es incorrecta, se debe lanzar un error, mientras que si es válida, debe retornar los datos de geolocalización. Esto se implementa agregando más casos en la lista de side effects, cubriendo así todas las situaciones posibles.

Aportes 6

Preguntas 0

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Hola a todos , en el reto solicitado , el servicio freeipapi no hace una validacion de la ip enviada por lo tanto en el metodo get\_location implementamos el control para la ip invalida Esto lo hacemos incluyendo el modulo ipaddress para validar la ip que se pasa como parametro ```js import requests import ipaddress def get_location(ip): ipaddress.ip_address(ip) url = f"https://freeipapi.com/api/json/{ip}" response = requests.get(url) print(response.status_code) print(response.raise_for_status()) data = response.json() #import ipdb; ipdb.set_trace() return { "country":data["countryName"], "region": data["regionName"], "city": data["cityName"] } ```A nivel de las pruebas implementamos la siguiente prueba unitaria ```js def test_get_location_invalid_ip_throw_exception(self): with self.assertRaises(ValueError): get_location("256.0.0.0") ``` def test\_get\_location\_invalid\_ip\_throw\_exception(self): with self.assertRaises(ValueError): get\_location("256.0.0.0")
Cuando vi esta clase me pregunté porqué se utiliza una lambda para definir el comportamiento del método `.json()`. Aquí va la respuesta: La l**ambda** en `json=lambda: {...}` asegura que cada llamada a `.json()` devuelva un nuevo diccionario, evitando efectos colaterales. Se usa en un **mock** de `requests.get` para simular una respuesta API realista. Primero, el mock lanza una `RequestException`, y luego devuelve un JSON válido con código `200`. Así, el test verifica que la función maneja errores y respuestas exitosas correctamente. Si se usara un diccionario estático en lugar de la **lambda**, `json()` siempre devolvería el mismo objeto, lo que podría causar efectos colaterales si el código bajo prueba modifica el diccionario. La **lambda** crea un nuevo diccionario en cada llamada, asegurando respuestas independientes. En el test, primero se simula un error con `RequestException`, y luego una respuesta `200` con datos de ubicación, validando que la función maneja ambos casos correctamente.
La propiedad `mock_get.side_effect` en Python es una característica del módulo `unittest.mock` que permite simular comportamientos más complejos en un objeto *mock*. Concretamente, `side_effect` se utiliza para definir lo que sucede cuando se llama al objeto *mock*. Puede configurarse para: 1. Lanzar excepciones. 2. Devolver diferentes valores en llamadas consecutivas. 3. Ejecutar una función personalizada.
No sé si estoy haciendo mal. En la función get\_location agregué la validación de ips ```js def get_location(ip): ipaddress.ip_address(ip) url = f"https://freeipapi.com/api/json/{ip}" response = requests.get(url) response.raise_for_status() data = response.json() return { "country": data["countryName"], "region": data["regionName"], "city": data["cityName"], "code": data["countryCode"], } ```Para realizar la prueba, si ejecuto únicamente la parte de la validación enviando una ip with self.assertRaises(ValueError): get\_location("8.8.0")incorrecta si ejecuta la prueba: ```js with self.assertRaises(ValueError): get_location("8.8.0") ```Pero si ejecuto también enviando una io válida, veo que la función del test ejeuta el request.get sin hacer uso del mock ```js @patch("src.api_client.requests.get") def test_get_location_return_side_effect_with_invalid_ip(self, mock_get): mock_get.side_effect = [ ValueError("8.8.0 does not appear to be an IPv4 or IPv6 address"), unittest.mock.Mock( status_code=200, json=lambda: { "countryName": "USA", "regionName": "Florida", "cityName": "MIAMI", "countryCode": "US", }, ), ] with self.assertRaises(ValueError): get_location("8.8.0") result = get_location("8.8.8.8") self.assertEqual(result.get("country"), "USA") self.assertEqual(result.get("region"), "Florida") self.assertEqual(result.get("city"), "MIAMI") self.assertEqual(result.get("code"), "US") ```¿Cómo sería la prueba para cumplir con el reto?: * Validar ip incorrecta * Ejecutar correctamente con IP correcta Auxilio!!!!
Para solucionar el reto, asegúrate de que el `mock` reemplaza correctamente el método `requests.get` en ambos casos, tanto para IPs válidas como inválidas. Utiliza el decorador `@patch` para modificar el comportamiento de `requests.get`. Asegúrate de que el `side_effect` en el mock devuelva el resultado esperado para las IPs correctas. Aquí tienes un ejemplo simplificado: ```python from unittest import TestCase from unittest.mock import patch class TestAPIClient(TestCase): @patch("src.api_client.requests.get") def test_ip_validation(self, mock_get): mock_get.side_effect = [ Exception("IP inválida"), # Caso para IP inválida mock_get.return_value # Caso para IP válida ] # Llamada a tu función que valida la IP result = your_function_to_test("valid_ip_here") # Afirmar el resultado esperado self.assertEqual(result, expected_value) ``` Asegúrate de que `mock_get` devuelva un objeto que simule la respuesta correcta de `requests.get` para IPs válidas. Esto debería resolver el problema y alinear los `assert` con el comportamiento esperado.
EL reto: Se usa la excepción HTTPError ```python @patch('src.api_client.requests.get') def test_get_location_return_ip_invalid(self, mock_get): mock_get.side_effect = [ requests.exceptions.HTTPError("Invalid ip"), unittest.mock.Mock( status_code=200, json= lambda: { "countryName": "USA", "regionName": "FLORIDA", "cityName": "MIAMI", "countryCode": "US", "language": "ENGLISH", } ) ] with self.assertRaises(requests.exceptions.HTTPError): get_location("8.8.8.8") result = get_location("8.8.8.8") self.assertEqual(result.get("country"), "USA") self.assertEqual(result.get("region"), "FLORIDA") self.assertEqual(result.get("city"), "MIAMI") self.assertEqual(result.get("code"), "US") self.assertEqual(result.get("language"), "ENGLISH") ```@patch('src.api\_client.requests.get') def test\_get\_location\_return\_ip\_invalid(self, mock\_get): mock\_get.side\_effect = \[ requests.exceptions.HTTPError("Invalid ip"), unittest.mock.Mock( status\_code=200, json= lambda: { "countryName": "USA", "regionName": "FLORIDA", "cityName": "MIAMI", "countryCode": "US", "language": "ENGLISH", } ) ] with self.assertRaises(requests.exceptions.HTTPError): get\_location("8.8.8.8") result = get\_location("8.8.8.8") self.assertEqual(result.get("country"), "USA") self.assertEqual(result.get("region"), "FLORIDA") self.assertEqual(result.get("city"), "MIAMI") self.assertEqual(result.get("code"), "US") self.assertEqual(result.get("language"), "ENGLISH")