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:
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:
Validan contratos: confirman que cada endpoint responde con el código y el formato que prometes.
Detectan regresiones: si alguien rompe la lógica de permisos, el test falla antes de que el bug llegue a producción.
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.
También lo noto necesario, seria un buen próximo curso.
Concuerdo x3
Hice esta prueba para verificar que un doctor pueda ser puesto en vacaciones:
classDoctorViewSetTests(TestCase): def setUp(self): self.patient=Patient.objects.create( first_name='Luis', last_name='Martinez', date_of_birth='1990-12-05', contact_number='12312312', email='example@example.com', address='Dirección de prueba', medical_history='Ninguna',) self.doctor=Doctor.objects.create( first_name='Oscar', last_name='Barajas', qualification='Profesional', contact_number='23412341234', email='example2@example.com', address='Medellín', biography='Sin', is_on_vacation=False,) self.client=APIClient() def test_set_on_vacation(self):"""Prueba para poner un doctor en vacaciones""" url =reverse('doctor-set-on-vacation', kwargs={"pk": self.doctor.id}, # Genera la URL para el doctor específico
) # Realizar la solicitud POST para poner al doctor en vacaciones
response = self.client.post(url) # Verificar que la respuesta sea 200OK self.assertEqual(response.status_code, status.HTTP_200_OK) # Refrescar el objeto doctor de la base de datos para verificar el cambio
self.doctor.refresh_from_db() # Verificar que el campo 'is_on_vacation' ahora sea True self.assertTrue(self.doctor.is_on_vacation) # Verificar que la respuesta contiene el mensaje adecuado
self.assertEqual(response.data['status'],"El doctor está en vacaciones")
Genial!
Para realizar pruebas unitarias de **endpoints anidados** usando APIClient en Django REST Framework, puedes simular las solicitudes HTTP a tus endpoints dentro de un contexto de prueba. Aquí te explico cómo hacerlo paso a paso.
### 1. **Escenario: Endpoints Anidados**
Supongamos que tienes un endpoint de **doctores** y uno de **pacientes** anidado bajo los doctores. Un ejemplo de URLs anidadas podría ser algo como:
- /api/doctors/ → Lista de doctores.
- /api/doctors/{doctor\_id}/patients/ → Lista de pacientes para un doctor en particular.
### 2. **Configuración de las Pruebas**
Primero, asegúrate de tener configurado tu APIClient y tus modelos.
#### Modelos
Si tienes los siguientes modelos:
\# models.py
from django.db import models
classDoctor(models.Model):  name = models.CharField(max\_length=100)  specialization = models.CharField(max\_length=100)classPatient(models.Model):  name = models.CharField(max\_length=100)  doctor = models.ForeignKey(Doctor, related\_name='patients', on\_delete=models.CASCADE)
#### Serializers
Los serializers podrían verse así:
\# serializers.py
from rest\_framework import serializers
from.models import Doctor, Patient
classPatientSerializer(serializers.ModelSerializer):  class Meta:  model = Patient  fields = \['id', 'name']classDoctorSerializer(serializers.ModelSerializer):  patients = PatientSerializer(many=True, read\_only=True)  class Meta:  model = Doctor  fields = \['id', 'name', 'specialization', 'patients']
from rest\_framework.routers import DefaultRouter
from.views import DoctorViewSet, PatientViewSet
router = DefaultRouter()router.register(r'doctors', DoctorViewSet)
\# URL anidadas para pacientes de un doctor específico
from rest\_framework\_nested import routers
doctor\_router = routers.NestedSimpleRouter(router,r'doctors', lookup='doctor')doctor\_router.register(r'patients', PatientViewSet, basename='doctor-patients')urlpatterns = router.urls + doctor\_router.urls
### 3. **Prueba de Endpoints Anidados**
Para realizar una prueba unitaria sobre los endpoints anidados usando APIClient, sigue los pasos siguientes:
#### Crear el archivo de pruebas
\# tests.py
from rest\_framework.test import APITestCase, APIClient
from django.urls import reverse
from rest\_framework import status
from.models import Doctor, Patient
classDoctorPatientAPITests(APITestCase):  def setUp(self):  self.client = APIClient()     \# Crear un doctor y pacientes  self.doctor = Doctor.objects.create(name="Dr. Smith", specialization="Cardiology")  self.patient1 = Patient.objects.create(name="John Doe", doctor=self.doctor)  self.patient2 = Patient.objects.create(name="Jane Doe", doctor=self.doctor)     \# URL para obtener la lista de pacientes de un doctor específico  self.patients\_url = reverse('doctor-patients-list', kwargs={'doctor\_pk': self.doctor.id})  def test\_get\_patients\_for\_doctor(self):  """Prueba para obtener la lista de pacientes de un doctor específico"""     \# Simular una solicitud GET al endpoint de pacientes  response = self.client.get(self.patients\_url)     \# Comprobar que la respuesta tenga código 200 (éxito)  self.assertEqual(response.status\_code, status.HTTP\_200\_OK)     \# Verificar que los pacientes se devuelvan correctamente en la respuesta  self.assertEqual(len(response.data), 2)  self.assertEqual(response.data\[0]\['name'], self.patient1.name)  self.assertEqual(response.data\[1]\['name'], self.patient2.name)  def test\_create\_patient\_for\_doctor(self):  """Prueba para crear un paciente bajo un doctor específico"""     \# Datos para el nuevo paciente  data = {  'name': 'Tom Doe'  }     \# Simular una solicitud POST al endpoint de pacientes  response = self.client.post(self.patients\_url, data, format='json')     \# Comprobar que la respuesta tenga código 201 (creado)  self.assertEqual(response.status\_code, status.HTTP\_201\_CREATED)     \# Verificar que el paciente fue creado correctamente y asociado al doctor  self.assertEqual(Patient.objects.filter(doctor=self.doctor).count(), 3)  self.assertEqual(response.data\['name'], 'Tom Doe')
### Explicación de la Prueba
1. **setUp**: Configuramos el cliente de la API (APIClient), creamos un Doctor y dos Patient. También generamos la URL de los pacientes anidados bajo un doctor específico.
2. **test\_get\_patients\_for\_doctor**:
- Enviamos una solicitud GET al endpoint anidado /doctors/{doctor\_id}/patients/.
- Verificamos que la respuesta tenga código de estado 200 OK.
- Comprobamos que se devuelvan los pacientes correctos en la respuesta.
3. **test\_create\_patient\_for\_doctor**:
- Enviamos una solicitud POST para crear un nuevo paciente bajo el doctor.
- Verificamos que el código de estado sea 201 CREATED.
- Comprobamos que el paciente fue correctamente añadido a la base de datos y asociado al doctor.
### 4. **Ejecutar las pruebas**
Para ejecutar las pruebas, utiliza el siguiente comando en la terminal:
python manage.py test
Esto ejecutará las pruebas en tu proyecto y validará que los endpoints anidados funcionen correctamente.
Podemos hacer Test similando el login. Aquí les dejo mis pruebas unitartias para doctor.
Tengo una pregunta que me surgió desde cursos anteriores como Flask, FastAPI y Django. Vengo del mundo de Typescript, y es que hay dos mundos muy diferentes, UnitTests y E2E.
En Typescript o Javascript estas pruebas que se han hecho serían pruebas E2E, en las Api de Python existen pruebas unitarias como las de Javascript? Probar de forma aislada una clase, un método, una función?