Las pruebas deben ser determinísticas: el mismo código, con los mismos datos, debería producir el mismo resultado. Fechas, horas, números aleatorios e identificadores generados pueden romper esa estabilidad.
En este tema veremos cómo controlar esas fuentes de variación usando inyección de dependencias, patch, monkeypatch y mocks.
Supongamos esta función:
from datetime import date
def crear_numero_factura(cliente_id):
hoy = date.today()
return f"FAC-{hoy.year}-{cliente_id}"
El resultado cambia según el año actual. La prueba debería controlar la fecha para no depender del día en que se ejecuta.
Si la función está en facturas.py, parcheamos el nombre date usado por ese módulo:
from datetime import date
from unittest.mock import patch
from facturas import crear_numero_factura
def test_crear_numero_factura():
with patch("facturas.date") as date_mock:
date_mock.today.return_value = date(2026, 5, 15)
numero = crear_numero_factura("CLI-10")
assert numero == "FAC-2026-CLI-10"
La prueba ya no depende de la fecha real.
Otra opción, muchas veces más simple, es recibir la fecha como parámetro:
def crear_numero_factura(cliente_id, hoy):
return f"FAC-{hoy.year}-{cliente_id}"
La prueba:
from datetime import date
def test_crear_numero_factura_con_fecha_inyectada():
numero = crear_numero_factura("CLI-10", date(2026, 5, 15))
assert numero == "FAC-2026-CLI-10"
Si el diseño lo permite, inyectar la fecha suele ser más claro que parchear.
Cuando muchas funciones necesitan la fecha actual, podemos inyectar una función reloj:
def crear_numero_factura(cliente_id, obtener_hoy):
hoy = obtener_hoy()
return f"FAC-{hoy.year}-{cliente_id}"
Prueba:
def test_crear_numero_factura_con_reloj():
def hoy_fijo():
return date(2026, 5, 15)
numero = crear_numero_factura("CLI-10", hoy_fijo)
assert numero == "FAC-2026-CLI-10"
Función:
from datetime import datetime
def registrar_evento(nombre):
return {
"nombre": nombre,
"creado_en": datetime.now().isoformat(),
}
Prueba con patch:
from datetime import datetime
from unittest.mock import patch
def test_registrar_evento():
fecha_fija = datetime(2026, 5, 15, 10, 30, 0)
with patch("eventos.datetime") as datetime_mock:
datetime_mock.now.return_value = fecha_fija
evento = registrar_evento("login")
assert evento == {
"nombre": "login",
"creado_en": "2026-05-15T10:30:00",
}
De nuevo, se parchea el nombre usado por el módulo bajo prueba.
Función:
from uuid import uuid4
def crear_id_pedido():
return f"PED-{uuid4()}"
Prueba:
from unittest.mock import patch
def test_crear_id_pedido():
with patch("pedidos.uuid4") as uuid4_mock:
uuid4_mock.return_value = "ABC123"
pedido_id = crear_id_pedido()
assert pedido_id == "PED-ABC123"
uuid4_mock.assert_called_once_with()
Una alternativa sin patch:
def crear_id_pedido(generar_uuid):
return f"PED-{generar_uuid()}"
Prueba:
def test_crear_id_pedido_con_generador_inyectado():
pedido_id = crear_id_pedido(lambda: "ABC123")
assert pedido_id == "PED-ABC123"
Este enfoque reduce el acoplamiento con imports y facilita pruebas simples.
Función:
import random
def elegir_descuento():
return random.choice([5, 10, 15])
Prueba con patch:
from unittest.mock import patch
def test_elegir_descuento():
with patch("promociones.random.choice", return_value=10) as choice_mock:
descuento = elegir_descuento()
assert descuento == 10
choice_mock.assert_called_once_with([5, 10, 15])
También podemos diseñar la función para recibir el selector:
def elegir_descuento(elegir):
return elegir([5, 10, 15])
Prueba:
def test_elegir_descuento_con_selector_inyectado():
def elegir_fijo(opciones):
return 10
assert elegir_descuento(elegir_fijo) == 10
Si necesitamos verificar las opciones, usamos un mock:
from unittest.mock import Mock
def test_elegir_descuento_verifica_opciones():
elegir = Mock(return_value=10)
descuento = elegir_descuento(elegir)
assert descuento == 10
elegir.assert_called_once_with([5, 10, 15])
Función:
import random
def crear_pin():
return str(random.randint(1000, 9999))
Prueba con monkeypatch:
import seguridad.pin as pin
def test_crear_pin(monkeypatch):
monkeypatch.setattr(pin.random, "randint", lambda inicio, fin: 1234)
assert pin.crear_pin() == "1234"
Otra técnica es usar una semilla con random.seed. Puede servir en algunos casos, pero suele ser menos explícita que inyectar o parchear el generador.
random.seed(10)
El problema es que la prueba queda atada a la implementación exacta del generador y al orden de llamadas. Para pruebas unitarias, preferimos controlar directamente el valor.
Si el código genera varios identificadores, podemos usar side_effect:
def crear_ids(cantidad, generar_uuid):
return [f"ID-{generar_uuid()}" for _ in range(cantidad)]
Prueba:
from unittest.mock import Mock
def test_crear_ids():
generar_uuid = Mock()
generar_uuid.side_effect = ["A", "B", "C"]
ids = crear_ids(3, generar_uuid)
assert ids == ["ID-A", "ID-B", "ID-C"]
assert generar_uuid.call_count == 3
Para sistemas más grandes, puede ser útil crear un objeto reloj:
class RelojSistema:
def ahora(self):
return datetime.now()
El código recibe el reloj:
def crear_evento(nombre, reloj):
return {
"nombre": nombre,
"creado_en": reloj.ahora(),
}
En pruebas usamos un stub:
class RelojStub:
def ahora(self):
return datetime(2026, 5, 15, 10, 30, 0)
Ejemplo:
def test_crear_evento_con_reloj_stub():
evento = crear_evento("login", RelojStub())
assert evento == {
"nombre": "login",
"creado_en": datetime(2026, 5, 15, 10, 30, 0),
}
Este diseño evita parches y centraliza la dependencia temporal.
patch o monkeypatch cuando trabajes con código existente.Prueba esta función:
from datetime import datetime
from uuid import uuid4
def crear_evento_auditoria(usuario_id):
return {
"id": str(uuid4()),
"usuario_id": usuario_id,
"creado_en": datetime.now().isoformat(),
}
Haz que uuid4 devuelva "EVT-1" y que datetime.now() devuelva 2026-05-15 10:30:00. Supón que la función está en auditoria.py.
Una solución con patch:
from datetime import datetime
from unittest.mock import patch
from auditoria import crear_evento_auditoria
def test_crear_evento_auditoria():
fecha_fija = datetime(2026, 5, 15, 10, 30, 0)
with patch("auditoria.uuid4") as uuid4_mock, patch("auditoria.datetime") as datetime_mock:
uuid4_mock.return_value = "EVT-1"
datetime_mock.now.return_value = fecha_fija
evento = crear_evento_auditoria("USR-1")
assert evento == {
"id": "EVT-1",
"usuario_id": "USR-1",
"creado_en": "2026-05-15T10:30:00",
}
uuid4_mock.assert_called_once_with()
datetime_mock.now.assert_called_once_with()
La prueba parchea los nombres usados por el módulo auditoria.
Fechas, horas, aleatoriedad e identificadores generados deben controlarse para que las pruebas sean repetibles. Podemos usar inyección de dependencias cuando diseñamos el código, o patch y monkeypatch cuando necesitamos reemplazar nombres existentes.
En el próximo tema veremos cómo mockear llamadas HTTP con respuestas controladas.