Un fake es un doble de prueba que implementa una versión simple de una dependencia real. A diferencia de un stub, que suele devolver respuestas preparadas, un fake puede tener una lógica mínima funcional.
Un ejemplo clásico es un repositorio en memoria que reemplaza temporalmente una base de datos. No es la implementación real de producción, pero permite guardar y consultar datos durante la prueba.
En este tema veremos cómo usar fakes y objetos simulados simples sin convertirlos en sistemas complejos.
Un fake es una implementación alternativa, más simple que la real, pensada para pruebas.
Características habituales:
Supongamos que una aplicación usa un repositorio de usuarios. El repositorio real podría guardar datos en una base de datos. Para una prueba unitaria, podemos usar una versión en memoria.
class RepositorioUsuariosFake:
def __init__(self):
self.usuarios = {}
def guardar(self, usuario):
self.usuarios[usuario["id"]] = usuario
def buscar_por_id(self, usuario_id):
return self.usuarios.get(usuario_id)
Este fake permite guardar y buscar usuarios sin usar una base de datos real.
def registrar_usuario(usuario, repositorio):
repositorio.guardar(usuario)
return usuario["id"]
def test_registrar_usuario_guarda_usuario_en_repositorio():
repositorio = RepositorioUsuariosFake()
usuario = {"id": 1, "nombre": "Ana"}
usuario_id = registrar_usuario(usuario, repositorio)
assert usuario_id == 1
assert repositorio.buscar_por_id(1) == usuario
La prueba verifica que la unidad usa el repositorio y que el usuario queda disponible en la implementación fake.
Un stub devuelve respuestas controladas. Un fake tiene una implementación simple que puede responder según operaciones anteriores.
| Tipo | Ejemplo | Característica |
|---|---|---|
| Stub | Siempre devuelve el mismo usuario. | Respuesta fija o preparada. |
| Fake | Guarda usuarios en memoria y luego los busca. | Implementación simple funcional. |
Un mock verifica interacciones esperadas. Un fake ofrece una implementación alternativa simple.
Si solo queremos comprobar que se llamó a guardar, un mock puede servir. Si queremos trabajar con un repositorio simple que permita guardar y buscar datos, un fake puede ser más natural.
La elección depende de qué comportamiento queremos verificar.
Un fake puede representar un almacenamiento simple de carritos.
class RepositorioCarritosFake:
def __init__(self):
self.carritos = {}
def guardar(self, usuario_id, carrito):
self.carritos[usuario_id] = carrito
def obtener(self, usuario_id):
return self.carritos.get(usuario_id)
Este fake permite probar una unidad que guarda y recupera carritos sin persistencia real.
def agregar_item_a_carrito(usuario_id, item, repositorio):
carrito = repositorio.obtener(usuario_id) or []
carrito.append(item)
repositorio.guardar(usuario_id, carrito)
return carrito
def test_agregar_item_a_carrito_nuevo():
repositorio = RepositorioCarritosFake()
carrito = agregar_item_a_carrito(1, "teclado", repositorio)
assert carrito == ["teclado"]
assert repositorio.obtener(1) == ["teclado"]
El fake guarda el carrito en memoria. La prueba puede verificar el resultado sin base de datos.
Un fake puede ser útil cuando:
El fake debe ser pequeño y específico para la necesidad de la prueba.
No conviene usar un fake cuando:
Un fake complejo puede convertirse en otro sistema que también necesita pruebas.
La principal diferencia práctica de muchos fakes es que conservan estado durante la prueba.
repositorio = RepositorioUsuariosFake()
repositorio.guardar({"id": 1, "nombre": "Ana"})
usuario = repositorio.buscar_por_id(1)
El usuario buscado depende de lo que se guardó antes en el fake. Eso permite simular una dependencia con comportamiento básico.
Como los fakes pueden tener estado, debemos crear uno nuevo por prueba para evitar contaminación entre casos.
def test_un_caso():
repositorio = RepositorioUsuariosFake()
...
def test_otro_caso():
repositorio = RepositorioUsuariosFake()
...
No conviene compartir un fake mutable global entre muchas pruebas, porque puede generar dependencia de orden.
Un fake no debería contener reglas de negocio complejas. Si el fake empieza a decidir descuentos, permisos o estados de negocio, puede estar haciendo demasiado.
La regla general es:
Ejemplo riesgoso:
class RepositorioPromocionesFake:
def obtener_promocion(self, cliente, monto):
if cliente["tipo"] == "vip" and monto > 10000:
return {"descuento": 20}
if cliente["tipo"] == "vip":
return {"descuento": 15}
return {"descuento": 0}
Este fake contiene reglas de promoción. Si esas reglas son lo que queremos probar, deberían estar en la unidad productiva y no escondidas en el fake.
Un fake más claro se limita a almacenar datos preparados.
class RepositorioPromocionesFake:
def __init__(self, promociones):
self.promociones = promociones
def buscar_por_cliente(self, cliente_id):
return self.promociones.get(cliente_id)
La prueba controla qué promoción existe. La lógica de aplicar promoción queda en la unidad probada.
Usar un fake no prueba que la dependencia real funcione. Si reemplazamos una base de datos por un repositorio en memoria, no verificamos consultas SQL, índices, transacciones ni configuración real.
Por eso, una estrategia completa puede incluir:
Ambas pruebas responden preguntas diferentes.
| Doble | Qué hace | Ejemplo |
|---|---|---|
| Stub | Devuelve una respuesta controlada. | Cotización fija 250. |
| Mock | Verifica interacción. | Confirmar llamada a notificador. |
| Fake | Implementación simple funcional. | Repositorio en memoria. |
| Objeto real simple | Se usa directamente. | Una clase pequeña sin dependencias externas. |
Conviene nombrar los fakes de forma clara para evitar confusión con clases reales.
RepositorioUsuariosFakeServicioCotizacionFakeRepositorioCarritosEnMemoriaEl nombre debe comunicar que se trata de una implementación de prueba o en memoria.
Antes de usar un fake, revisa:
Los fakes permiten reemplazar dependencias reales con implementaciones simples y controladas. Son especialmente útiles cuando necesitamos una dependencia con estado, como un repositorio en memoria.
El valor de un fake está en simplificar la prueba sin ocultar la lógica importante. Si el fake se vuelve complejo, probablemente estamos desplazando el problema en lugar de resolverlo.
En el próximo tema veremos inyección de dependencias, una técnica que facilita reemplazar dependencias reales por dobles de prueba.