No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Pruebas de Mock para APIs con Python y UnitTest

11/20
Recursos

La simulaci贸n de servicios externos es crucial en proyectos de software para garantizar que las pruebas no dependan de APIs externas. Para lograrlo, utilizamos los Mocks, que permiten evitar las llamadas reales a servicios y, en cambio, retornan respuestas controladas en nuestras pruebas. En este caso, aprenderemos a mockear una API de geolocalizaci贸n y a realizar pruebas efectivas.

驴Qu茅 es un Mock y c贸mo nos ayuda?

Un Mock es una herramienta que nos permite simular comportamientos de funciones o servicios externos. En lugar de ejecutar una llamada real a una API, podemos definir una respuesta predefinida, lo que permite:

  • Evitar depender de servicios externos en pruebas.
  • Acelerar la ejecuci贸n de las pruebas.
  • Controlar los resultados esperados.

驴C贸mo integramos una API externa en Python?

Primero, se configura una funci贸n que recibe la IP del cliente y devuelve la ubicaci贸n mediante una API de terceros. Para hacer esto:

  1. Se instala la librer铆a requests con pip install requests.
  2. Se crea un archivo api_client.py donde conectamos con la API utilizando requests.get.
  3. Al recibir la respuesta, se convierte el resultado a JSON para obtener la informaci贸n de pa铆s, ciudad y regi贸n.

驴C贸mo probamos sin hacer llamadas reales?

El problema principal de las pruebas de integraciones con APIs es que pueden demorar, ya que las respuestas dependen de factores externos. Para evitar esto, se usan Mocks. A trav茅s del decorador @patch de unittest.mock, podemos interceptar la llamada a la API y retornar datos predefinidos.

Pasos a seguir:

  • Decorar la funci贸n de prueba con @patch.
  • Simular el valor retornado usando mock.return_value para definir qu茅 debe devolver la llamada a la API.
  • Definir tanto el c贸digo de estado como el contenido del JSON que esperamos recibir.

驴C贸mo validar que nuestra simulaci贸n funciona correctamente?

Adem谩s de simular respuestas, debemos asegurarnos de que las pruebas validen correctamente los llamados. Se puede usar assertCalledOnceWith para garantizar que la URL y los par谩metros pasados son los correctos.

Aportes 13

Preguntas 0

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

Lo m谩s probable es que lo sepan pero por s铆 no cuando el profe consigui贸 alinear todo de golpe lo que hizo fue presionar alt+ click izquierdo para seleccionar varias partes del c贸digo ... finalmente solo di贸 un enter Abrazo colegas
para los que esten en un entorno windows y no les funcione el `pip freeze | grep requests` deben de usar `pip freeze | Select-String -Pattern "requests"` Esto es debido a que `grep` no es nativo de Windows.
El equivalente de "grep" en windows cmd es "findstr"
Qu茅 tal gente, qu茅 buen curso, lo hubiera deseado hace 2 a帽os. Casi me despiden de un trabajo x n o saber moviese y escribir bien unittests. Les comento que para trabajar con requests hay una librer铆a que se llama requests_mock denle una probada.
El reto: ```python import requests def get_location(ip): url = f'https://freeipapi.com/api/json/{ip}' response = requests.get(url) response.raise_for_status() data = response.json() #print(data) return { "country": data["countryName"], "region": data["regionName"], "city": data["cityName"], "code": data["countryCode"], "language": data["language"] } if __name__=="__main__": print(get_location("8.8.8.8")) #test import unittest from src.api_client import get_location from unittest.mock import patch class ApiClientTest(unittest.TestCase): @patch('src.api_client.requests.get') def test_get_location_return_expected_data(self, mock_get): mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = { "countryName": "USA", "regionName": "FLORIDA", "cityName": "MIAMI", "countryCode": "US", "language": "ENGLISH", } 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") mock_get.assert_called_once_with("https://freeipapi.com/api/json/8.8.8.8") ```import requests def get\_location(ip): url = f'https://freeipapi.com/api/json/{ip}' response = requests.get(url) response.raise\_for\_status() data = response.json() #print(data) return { "country": data\["countryName"], "region": data\["regionName"], "city": data\["cityName"], "code": data\["countryCode"], "language": data\["language"] } if \_\_name\_\_=="\_\_main\_\_": print(get\_location("8.8.8.8"))
Cuando se utiliza ipdb, el profe uso ; Muy probablemente sea algo raro sino lo hab铆an visto ante por que en python no se usa ; no? La realidad es que si pero es muy raro. En este caso se usa para agrupar dos lineas de codigo en una
profe pero todo est茅 c贸digo no est谩 hardcodeado ? la idea es que funcione con valores que pudieran ser din谩micos o me equivoco ?
```js import requests def get_location(ip): url = f"https://freeipapi.com/api/json/{ip}" response = requests.get(url) response.raise_for_status() data = response.json() # import ipdb; ipdb.set_trace() print(data) return { "country": data["countryName"], "region": data["regionName"], "city": data["cityName"], "language": data["language"], "zip": data["zipCode"], } if __name__ == "__main__": location = get_location("8.8.8.8") print(location) ``````js import unittest from unittest.mock import patch from src.api_client import get_location class ApiClientTests(unittest.TestCase): @patch('src.api_client.requests.get') def test_get_location_returns_exected_data(self, mock_get): mock_get.return_value.status_code = 200 # mock_get.return_value.json.return_value = { # {'ipVersion': 4, # 'ipAddress': '8.8.8.8', # 'latitude': 37.386051, # 'longitude': -122.083847, # 'countryName': 'United States of America', # 'countryCode': 'US', # 'timeZone': '-07:00', # 'zipCode': '94035', # 'cityName': 'Mountain View', # 'regionName': 'California', # 'isProxy': False, # 'continent': 'Americas', # 'continentCode': 'AM', # 'currency': {'code': 'USD', 'name': 'US Dollar'}, # 'language': 'English', # 'timeZones': ['America/Adak', 'America/Anchorage', 'America/Boise', 'America/Chicago', 'America/Denver', 'America/Detroit', 'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo', 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', 'America/Indiana/Vevay', 'America/Indiana/Vincennes', 'America/Indiana/Winamac', 'America/Juneau', 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', 'America/Los_Angeles', 'America/Menominee', 'America/Metlakatla', 'America/New_York', 'America/Nome', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Phoenix', 'America/Sitka', 'America/Yakutat', 'Pacific/Honolulu'], # 'tlds': ['.us']} # } mock_get.return_value.json.return_value = { "countryName": "USA", "regionName": "FLORIDA", "cityName": "MIAMI", "language": "English", "zipCode": "331" } 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("language"), "English") self.assertEqual(result.get("zip"), "331") # Validando que se esta llamando a la URL correcta mock_get.assert_called_once_with("https://freeipapi.com/api/json/8.8.8.8") ```
Otra cosa que Comentar... si al ejecutar ipdb en la terminal e ingresar "data" les vota el siguiente error "signal.set\_wakeup\_fd(-1) ValueError: set\_wakeup\_fd only works in main thread" Lo mas probable es que su versi贸n de python sea el problema.... Yo tenia la 3.8 y no me permit铆a usar ipdb 脷nicamente descarguen la mas reciente y cambien sus variables de entorno para ejecutar el script con la version reciente
En que caso de que quieran hacerle un mock a m谩s de una funci贸n que se ejecuta dentro de otra funci贸n (por ejemplo, si tuvieran una base de datos con las IP's de tus clientes y quieres verificar no solo que la IP sea de un pa铆s aceptado, sino que adem谩s pertenece al cliente que est谩 intentando acceder) se puede hacer siguiendo la siguiente estructura: ```python @patch('nombre_modulo.funcion_a_parchear_1') @patch('nombre_modulo.funcion_a_parchear_2') def test_my_function(mock_func2, mock_func1): # mock_func2 corresponde a funcion_a_parchear_2 # mock_func1 corresponde a funcion_a_parchear_1 # Configurar returns de las funciones a mockear mock_func1.return_value = "mock_value1" mock_func2.return_value = "mock_value2" ``` Como vemos, tenemos que aplicar el decorador 2 veces y la funci贸n recibe los mock como par谩metros en el orden inverso al que se defini贸, es decir, si definiste un patch para mock de la \*funci贸n 1\* y abajo el de la \*funci贸n 2\*, el primer par谩metro mock de la funci贸n corresponder谩 al de la funci贸n que definimos de 煤ltimo (\*funci贸n 2\* en este caso) y de ah铆 vamos ascendiendo hasta llegar a la primera funci贸n a la que le hicimos el patch.
Ejemplo usando `pytest `y `mock:` ```python import pytest import httpx from unittest.mock import patch # Usa pytest-mock o el decorador patch de unittest para simular la solicitud @patch("httpx.Client.get") def test_perfil_endpoint_mock(mock_get): # Configuramos el mock para devolver una respuesta simulada mock_response = httpx.Response( status_code=200, json={ "rol": "Software Engineer", "description": "Soy Ingeniero Inform谩tico con s贸lida experiencia en el desarrollo de software.", "skills": ["python", "javascript", "fastapi"], "username": "jimcostdev", "avatar": "https://avatars.githubusercontent.com/u/53100460?v=4", "id": 1 } ) mock_get.return_value = mock_response # Simula el cliente HTTP with httpx.Client() as client: response = client.get("https://ficticious-url.local/test-endpoint") # Comprueba que los datos sean correctos assert response.status_code == 200 assert response.json()["username"] == "jimcostdev" assert response.json() == mock_response.json() ```
Hola otra forma muy interesante de "mockear" las respuestas es guardarlas en archivos .json y luego usarla , por ac谩 un ejemplo: ```js import unittest, json from unittest.mock import patch from src.api_client import get_location class ApiClientTest(unittest.TestCase): @patch('src.api_client.requests.get') def test_get_location_returns_expected_data(self, mock_get)-> None: mock_get.return_value.status_code = 200 mock_response = '' with open('tests/support/mock_freeapi.json', 'r') as f: mock_response = json.load(f) mock_get.return_value.json.return_value = mock_response result = get_location('8.8.8.8') self.assertEqual(result.get('country'), 'United States of America') self.assertEqual(result.get('region'), 'California') self.assertEqual(result.get('city'), 'Mountain View') mock_get.assert_called_once_with("https://freeipapi.com/api/json/8.8.8.8") ```
Reto: Test ```python @patch("src.api_client.requests.get") def test_get_location_returns_expected_data(self, mock_get): mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = { "countryName": "USA", "regionName": "Florida", "cityName": "Miami", "language": "English", "currency": {"code": "USD"}, "zipCode": "33101", } 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("language"), "English") self.assertEqual(result.get("currency"), "USD") self.assertEqual(result.get("zip_code"), "33101") mock_get.assert_called_once_with("https://freeipapi.com/api/json/8.8.8.8") ```@patch("src.api\_client.requests.get")聽 聽 def test\_get\_location\_returns\_expected\_data(self, mock\_get):聽 聽 聽 聽 mock\_get.return\_value.status\_code = 200聽 聽 聽 聽 mock\_get.return\_value.json.return\_value = {聽 聽 聽 聽 聽 聽 "countryName": "USA",聽 聽 聽 聽 聽 聽 "regionName": "Florida",聽 聽 聽 聽 聽 聽 "cityName": "Miami",聽 聽 聽 聽 聽 聽 "language": "English",聽 聽 聽 聽 聽 聽 "currency": {"code": "USD"},聽 聽 聽 聽 聽 聽 "zipCode": "33101",聽 聽 聽 聽 }聽 聽 聽 聽 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("language"), "English")聽 聽 聽 聽 self.assertEqual(result.get("currency"), "USD")聽 聽 聽 聽 self.assertEqual(result.get("zip\_code"), "33101") 聽 聽 聽 聽 mock\_get.assert\_called\_once\_with("https://freeipapi.com/api/json/8.8.8.8") Fun. get\_location ```python def get_location(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"], "language": data["language"], "currency": data["currency"]["code"], "zip_code": data["zipCode"], } ```def get\_location(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"],聽 聽 聽 聽 "language": data\["language"],聽 聽 聽 聽 "currency": data\["currency"]\["code"],聽 聽 聽 聽 "zip\_code": data\["zipCode"],聽 聽 }