Testing unitario en Django

Intermedio
Django
Django
Actualizado: 18/04/2026

Framework de testing de Django

Django extiende unittest.TestCase con su propia clase TestCase que proporciona:

Diagrama conceptual de Testing unitario en Django

  • Una base de datos de test aislada que se recrea en cada método de test.
  • Fixtures para cargar datos iniciales.
  • Assertions especializadas para Django.
  • Un cliente HTTP simulado para testar vistas.

Estructura básica de un test

# catalogo/tests.py o catalogo/tests/test_models.py
from django.test import TestCase
from django.utils import timezone
from .models import Producto, Categoria

class ProductoModelTest(TestCase):
    """Tests del modelo Producto."""

    @classmethod
    def setUpTestData(cls):
        """
        Se ejecuta una vez para toda la clase de tests (más eficiente que setUp).
        No modificar los objetos creados aquí en los tests individuales.
        """
        cls.categoria = Categoria.objects.create(
            nombre='Electrónica',
            slug='electronica'
        )

    def setUp(self):
        """Se ejecuta antes de cada método de test individual."""
        self.producto = Producto.objects.create(
            nombre='Laptop Test',
            precio=999.99,
            categoria=self.categoria,
            activo=True
        )

    def tearDown(self):
        """Se ejecuta después de cada test (raramente necesario con TestCase)."""
        pass

    def test_str_devuelve_nombre(self):
        """__str__ debe devolver el nombre del producto."""
        self.assertEqual(str(self.producto), 'Laptop Test')

    def test_precio_positivo(self):
        """El precio debe ser positivo."""
        self.assertGreater(self.producto.precio, 0)

    def test_producto_activo_por_defecto(self):
        """Los productos se crean activos por defecto."""
        producto = Producto.objects.create(
            nombre='Nuevo',
            precio=10.00,
            categoria=self.categoria
        )
        self.assertTrue(producto.activo)

    def test_get_absolute_url(self):
        """get_absolute_url debe devolver la URL correcta."""
        url = self.producto.get_absolute_url()
        self.assertIn(str(self.producto.pk), url)

    def test_manager_activos_filtra_inactivos(self):
        """El manager 'activos' no debe devolver productos inactivos."""
        self.producto.activo = False
        self.producto.save()
        activos = Producto.activos.all()
        self.assertNotIn(self.producto, activos)

Tests de formularios

from django.test import TestCase
from .forms import ProductoForm, RegistroForm

class ProductoFormTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.categoria = Categoria.objects.create(nombre='Test', slug='test')

    def test_formulario_valido(self):
        datos = {
            'nombre': 'Producto válido',
            'precio': '29.99',
            'categoria': self.categoria.pk,
        }
        form = ProductoForm(data=datos)
        self.assertTrue(form.is_valid())

    def test_nombre_muy_corto_invalido(self):
        datos = {
            'nombre': 'A',  # Muy corto
            'precio': '29.99',
            'categoria': self.categoria.pk,
        }
        form = ProductoForm(data=datos)
        self.assertFalse(form.is_valid())
        self.assertIn('nombre', form.errors)

    def test_precio_negativo_invalido(self):
        datos = {
            'nombre': 'Producto test',
            'precio': '-5.00',
            'categoria': self.categoria.pk,
        }
        form = ProductoForm(data=datos)
        self.assertFalse(form.is_valid())
        self.assertIn('precio', form.errors)

    def test_campos_obligatorios(self):
        form = ProductoForm(data={})
        self.assertFalse(form.is_valid())
        self.assertIn('nombre', form.errors)
        self.assertIn('precio', form.errors)

Tests de señales

from unittest.mock import patch, MagicMock
from django.test import TestCase
from django.contrib.auth import get_user_model

User = get_user_model()

class PerfilSignalTest(TestCase):
    def test_perfil_se_crea_al_registrar_usuario(self):
        """Verificar que post_save crea el perfil automáticamente."""
        usuario = User.objects.create_user(
            username='test_signal',
            email='test@test.com',
            password='pass123'
        )
        self.assertTrue(hasattr(usuario, 'perfil'))
        self.assertIsNotNone(usuario.perfil)

    def test_perfil_no_se_duplica_al_actualizar(self):
        """Actualizar el usuario no debe crear un perfil duplicado."""
        from usuarios.models import Perfil
        usuario = User.objects.create_user(username='test_dup', password='pass')
        usuario.first_name = 'Ana'
        usuario.save()
        perfiles = Perfil.objects.filter(usuario=usuario)
        self.assertEqual(perfiles.count(), 1)

Tests de managers

class ProductoManagerTest(TestCase):
    def setUp(self):
        cat = Categoria.objects.create(nombre='Cat', slug='cat')
        Producto.objects.create(nombre='Activo 1', precio=10, categoria=cat, activo=True)
        Producto.objects.create(nombre='Activo 2', precio=20, categoria=cat, activo=True)
        Producto.objects.create(nombre='Inactivo', precio=5, categoria=cat, activo=False)

    def test_manager_activos_count(self):
        self.assertEqual(Producto.activos.count(), 2)

    def test_manager_en_oferta(self):
        cat = Categoria.objects.first()
        Producto.objects.create(
            nombre='Oferta',
            precio=100,
            precio_oferta=80,
            categoria=cat,
            activo=True
        )
        self.assertEqual(Producto.objects.en_oferta().count(), 1)

Ejecutar los tests

# Ejecutar todos los tests
python manage.py test

# Ejecutar tests de una aplicación
python manage.py test catalogo

# Ejecutar una clase de tests específica
python manage.py test catalogo.tests.ProductoModelTest

# Ejecutar un test específico
python manage.py test catalogo.tests.ProductoModelTest.test_str_devuelve_nombre

# Ejecutar con verbosidad alta
python manage.py test --verbosity 2

# Mantener la base de datos de test (más rápido en ejecuciones repetidas)
python manage.py test --keepdb
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Crear tests unitarios con la clase TestCase de Django. Usar setUp() y tearDown() para preparar y limpiar el estado de los tests. Testear la lógica de negocio de los modelos Django. Verificar la validación de formularios en los tests. Testear señales y managers personalizados con assertions específicos.