Tests de API con APIClient en Django

Resumen

Probar tu API antes de mandarla a producción te ahorra horas de debugging y te protege de regresiones silenciosas. Con Django Rest Framework puedes escribir unit tests en Django usando la clase APIClient, que simula requests reales contra tus endpoints sin levantar un servidor.

Esta guía te muestra cómo estructurar pruebas para un viewset, validar códigos de respuesta y detectar bugs de permisos antes de que lleguen a tus usuarios.

Cómo se configura un test case en Django Rest Framework

Todo arranca en el archivo tests.py que Django genera automáticamente en cada aplicación. Ahí ya viene importada la clase TestCase, de la cual heredas para acceder a las utilidades de pruebas del framework.

La estructura básica empieza con una clase nombrada según lo que vas a validar. Si pruebas un viewset de appointments, una buena convención es AppointmentViewsetTest(TestCase). Dentro defines un método setUp, que se ejecuta antes de cada prueba y te permite reutilizar datos.

¿Para qué sirve el método setUp en un TestCase? Para crear datos que se usan en todas las pruebas de la clase, como un paciente o un doctor. Se ejecuta antes de cada test y evita repetir código.

Cómo crear datos de prueba con el ORM

Dentro de setUp aprovechas el ORM de Django para crear objetos directamente en la base de datos de pruebas. En el ejemplo se crean dos modelos:

  • Un paciente con nombre Luis Martínez, fecha de nacimiento 1990-12-05, contacto, email y dirección.
  • Un doctor con nombre Óscar Barajas, especialidad profesional, email, dirección Medellín y vacation=False.
  • Un cliente de pruebas instanciado como self.client = APIClient().

El APIClient se importa con from rest_framework.test import APIClient y trae configuraciones predeterminadas, como tratar todo el contenido como JSON. Eso te ahorra definir headers manualmente en cada request [03:30].

Cómo escribir un test que valide un endpoint

Un test es un método dentro de la clase cuyo nombre empieza con test_. La convención recomendada describe la expectativa, por ejemplo test_list_should_return_200. Dentro del método arma la URL, ejecuta el request y compara el resultado.

Para construir URLs sin hardcodearlas se usa reverse, importado con from django.urls import reverse. Esta función traduce el nombre de una URL a su path real, así que si cambias la ruta en urls.py, tus tests siguen funcionando.

¿Qué hace reverse en Django? Convierte el nombre de una URL en su path completo. Si la URL recibe parámetros, los pasas como diccionario en kwargs, por ejemplo reverse('appointments-list', kwargs={'pk': doctor.id}).

Cómo descubrir los nombres de tus URLs

Para conocer los nombres registrados de tus rutas existe una herramienta muy útil: la extensión django-extensions. La instalas con:

bash pip install django-extensions

Después la agregas a INSTALLED_APPS en settings.py como django_extensions (con guion bajo, no guion medio). Eso habilita el comando python manage.py show_urls, que lista todas las rutas del proyecto junto con su nombre y los parámetros que reciben [05:50].

Cómo validar el response con assertEqual

Una vez tienes la URL, simulas el request con el cliente:

python response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK)

El método assertEqual compara dos valores y falla la prueba si no coinciden. Para los códigos HTTP conviene importar from rest_framework import status y usar constantes legibles como status.HTTP_200_OK o status.HTTP_403_FORBIDDEN en lugar de números sueltos.

Por qué los tests unitarios detectan bugs de permisos

Al correr la prueba con python manage.py test, el primer resultado fue un fallo: el endpoint devolvía 403 Forbidden en vez de 200 OK. Lejos de ser un problema, ese error reveló un bug real en la configuración de permisos del viewset.

El código tenía una combinación de IsAuthenticatedOrReadOnly con un permiso personalizado IsDoctor, pero la lógica no estaba dejando pasar peticiones sin autenticación como debería. La prueba expuso que el comportamiento esperado y el real no coincidían.

La solución fue ajustar los permisos a IsAuthenticated combinado con IsDoctor, dejando claro que solo doctores autenticados pueden acceder. Después de ese cambio, la prueba se actualizó para esperar 403 cuando el usuario no está autenticado, y pasó exitosamente [09:40].

Qué te enseña este flujo sobre testing

Este pequeño ejercicio resume tres beneficios concretos de las pruebas unitarias:

  1. Validan contratos: confirman que cada endpoint responde con el código y el formato que prometes.
  2. Detectan regresiones: si alguien rompe la lógica de permisos, el test falla antes de que el bug llegue a producción.
  3. Documentan comportamiento: leer los tests es leer las reglas del negocio sin abrir la documentación.

El APIClient de Django Rest Framework es la pieza central porque te deja simular cualquier verbo HTTP (GET, POST, PUT, DELETE) sin levantar el servidor ni configurar herramientas externas. Combinado con setUp, reverse y las constantes de status, tienes todo para cubrir tu API con pruebas rápidas y mantenibles.

¿Qué endpoint vas a cubrir primero con tests? Cuéntame en los comentarios qué parte de tu API te genera más dudas al momento de probarla.