El Trofeo del Testing 🏆
Qué es y de dónde viene
El Testing Trophy es un modelo de distribución de esfuerzo en testing propuesto por Kent C. Dodds en 2018, como evolución del Testing Pyramid clásico de Martin Fowler (2012). Fue inspirado por el tweet de Guillermo Rauch (creador de Socket.io, fundador de Vercel):
"Write tests. Not too many. Mostly integration."
La idea central: el retorno sobre la inversión (ROI) de los tests no es uniforme. Escribir más tests no da necesariamente más confianza — importa qué tipo de tests escribes y en qué proporción.
El Trofeo vs. la Pirámide
| Aspecto | Pirámide (Fowler, 2012) | Trofeo (Dodds, 2018) |
|---|---|---|
| Capa más grande | Unit tests | Integration tests |
| Premisa principal | Los E2E son lentos y caros — evítalos | Las herramientas mejoraron; el ROI cambia |
| Incluye análisis estático | No | Sí (base de la pirámide) |
| Foco | Velocidad de ejecución | Confianza real por inversión de tiempo |
| Contexto original | Backend / lenguajes compilados | Frontend / JavaScript / TypeScript |
La Pirámide asume que los E2E son prohibitivamente lentos y costosos. Esa premisa era cierta en 2012 con Selenium. Con Playwright y Vitest Browser Mode en 2025, ya no lo es.
El principio guía del Trofeo:
"The more your tests resemble the way your software is used, the more confidence they can give you." — Kent C. Dodds
Estructura del Trofeo
▲
/E\ ← pocos, lentos, máxima confianza
/2E \
/─────\
/ \
/ Integr. \ ← LA MAYOR INVERSIÓN
/ tests \
/─────────────\
/ Unit tests \ ← rápidos, bajo costo, confianza acotada
/─────────────────\
/ Static analysis \ ← TypeScript, ESLint — sin costo de ejecución
────────────────────
Nivel 0 — Análisis estático (base, costo cero en runtime)
TypeScript y ESLint detectan errores antes de ejecutar cualquier código. No son "tests" en sentido estricto pero proveen la primera capa de confianza sin costo de ejecución. En Wiedii son obligatorios en todos los proyectos JS/TS.
Qué captura: errores de tipos, variables no usadas, imports mal formados, violaciones de convención. Qué NO captura: lógica de negocio incorrecta, interacciones entre módulos.
Nivel 1 — Unit tests (base del trofeo)
Prueban una única función, clase u objeto en aislamiento total — sus dependencias están mockeadas o ausentes.
Cuándo son valiosos:
- Lógica pura (funciones matemáticas, parsers, transformadores de datos)
- Algoritmos con muchos casos borde (binary search, validaciones complejas)
- Utilidades independientes reutilizadas en múltiples lugares
Cuándo NO son la respuesta:
- Cuando la función A llama a la función B — un unit test de A con B mockeada no prueba que A+B trabajen juntas
- Cuando el comportamiento que importa es la interacción entre capas
Regla práctica: antes de escribir un unit test, pregunta: ¿un integration test que ya existe cubre este path? Si sí, el unit test es redundante.
Nivel 2 — Integration tests (el corazón del trofeo — LA MAYOR INVERSIÓN)
Prueban múltiples unidades interactuando entre sí — con tan poco mocking como sea posible.
Son el núcleo del Trofeo porque ofrecen el mejor balance entre:
- Velocidad de escritura y ejecución (mucho más rápidos que E2E)
- Confianza real (prueban comportamiento observable, no implementación)
- Resistencia al refactor (si cambias cómo está implementado algo pero el comportamiento es el mismo, el test no debe romperse)
Qué mockear en integration tests:
- Servicios externos reales (APIs de terceros, pasarelas de pago) — usar MSW u otro interceptor HTTP
- Efectos secundarios costosos (emails, notificaciones push)
Qué NO mockear:
- Tu propia lógica de negocio
- Tu propia base de datos (usar una instancia de prueba real o en memoria)
- Tus propios módulos internos
Señal de que tienes demasiados mocks: si tu integration test mockeó 5 cosas para probar una función, probablemente es un unit test disfrazado.
Nivel 3 — Property-based tests (capa transversal — adición de Wiedii)
Los tests de propiedad prueban invariantes que deben sostenerse para cualquier input dentro de un dominio, no para inputs concretos hardcodeados.
Wiedii usa fast-check integrado con bun:test. Son complementarios — no reemplazan unit ni integration tests, sino que los refuerzan cuando existe lógica con muchos valores de entrada posibles.
import * as fc from 'fast-check';
import { describe, test, expect } from 'bun:test';
describe('serialize / deserialize — invariante de ida y vuelta', () => {
test('siempre recupera el valor original', () => {
fc.assert(
fc.property(fc.record({ id: fc.uuid(), name: fc.string() }), (obj) => {
expect(deserialize(serialize(obj))).toEqual(obj);
}),
);
});
});
Cuándo añadir property tests:
- Funciones de serialización / parseo — propiedad:
deserialize(serialize(x)) === x - Operaciones conmutativas o asociativas
- Funciones de ordenamiento — propiedad: el resultado siempre está ordenado
- Validadores — propiedad: inputs válidos nunca se rechazan, inputs inválidos nunca se aceptan
- Cualquier función donde "para todos los X válidos, Y debe cumplirse"
Cuándo NO añadir property tests:
- Comportamientos con semántica específica por caso (reglas de negocio que cambian por país, por tipo de usuario, etc.)
- Flujos de UI — ahí van integration o E2E tests
Nivel 4 — End-to-End tests (cima del trofeo)
Prueban el sistema completo sin mocks — exactamente como lo usaría un usuario real. Desde la UI hasta la base de datos, pasando por toda la pila.
Son los tests que dan más confianza por definición, pero históricamente eran los más costosos. En 2025 esto cambió: con Playwright, los E2E son significativamente más rápidos y confiables que con Selenium. Kent C. Dodds ha planteado públicamente que en algunos contextos modernos (SSR, full-stack frameworks), el E2E podría convertirse en la capa más grande del trofeo.
Qué cubrir con E2E:
- Los flujos críticos de negocio — login, checkout, onboarding, operaciones irreversibles
- Happy paths de las features más importantes
- Flujos que cruzan muchos servicios y donde una integration test no puede cubrir la integración real
Qué NO cubrir con E2E:
- Casos borde — son lentos y costosos para eso; los cubren los integration tests
- Cada posible estado de UI — usa integration tests con estados simulados
Estado actual en la industria (2025-2026)
El Trofeo es la referencia más citada en el ecosistema JavaScript/TypeScript. El panorama actual:
| Arquitectura | Modelo más adoptado | Proporción |
|---|---|---|
| Monolitos | Testing Pyramid | ~61% |
| Microservicios | Testing Honeycomb (Spotify) | ~48% |
| Serverless / FaaS | Testing Trophy | ~42% |
| Full-stack JS/TS | Testing Trophy | mayoría |
Ningún modelo es universalmente correcto. El consenso actual en la industria:
- Para lógica de dominio compleja (reglas de negocio, algoritmos, cálculos) → más unit tests, la Pirámide funciona bien
- Para aplicaciones orientadas a API, SSR o flujos de usuario → el Trofeo es superior
- La mayoría de proyectos exitosos usan un híbrido: muchos integration tests como columna vertebral, unit tests donde la lógica es compleja, E2E para journeys críticos
La pregunta que se está debatiendo activamente en 2025-2026 (incluido el propio Kent C. Dodds): con Playwright y el browser mode de Vitest haciendo los E2E tan baratos como los integration tests, ¿deberían los E2E convertirse en la mayor inversión? La respuesta aún no es definitiva, pero la tendencia apunta a que los E2E van a crecer en proporción relativa a medida que las herramientas mejoran.
Cómo aplica en Wiedii
Por tipo de proyecto
| Tipo de proyecto | Modelo | Inversión principal |
|---|---|---|
| Web app / API / SSR | Testing Trophy | Integration tests |
| CLI tool pura (sin DB, sin web) | Pyramid adaptado | Unit tests + integration de comandos |
| Librería/paquete reutilizable | Pyramid | Unit tests exhaustivos |
| Monorepo con múltiples capas | Trophy por capa | Integration entre paquetes |
Distribución orientativa del esfuerzo
E2E → ~10-15% (journeys críticos, no casos borde)
Property-based → ~5-10% (invariantes de lógica pura)
Integration → ~60-70% (EL NÚCLEO — aquí va la mayor inversión)
Unit → ~15-20% (lógica compleja aislada)
Static → 100% (TypeScript + ESLint en todo el código)
Estos porcentajes son orientativos — no son reglas fijas. El objetivo es la confianza, no alcanzar un número.
Framework por tipo de proyecto
| Tipo de proyecto | Framework de testing | Notas |
|---|---|---|
| CLI tools (sin DB, sin web) | bun:test | Nativo, rápido, suficiente |
| Web apps / APIs / full-stack | Vitest (a evaluar) | Ver bun-testing para la política completa |
| Property-based (cualquier tipo) | fast-check + el runner del proyecto | Compatible con bun:test y Vitest |
| E2E | Playwright | Estándar de facto en 2025 |
Qué NO hacer
Demasiados unit tests con mocks excesivos — el error más común al intentar "cubrir" código. Un test que mockea 80% de las dependencias de una función da falsa confianza: el código puede pasar los tests y romperse en producción porque la integración real falla.
E2E para todo — la cima del trofeo es pequeña por una razón. Usar E2E para probar cada campo de formulario o cada mensaje de error hace los tests frágiles, lentos y difíciles de mantener.
100% de cobertura de código como objetivo — la cobertura es un indicador, no el objetivo. El 100% puede lograrse con tests que no detectan bugs reales. El objetivo es la confianza en que el sistema funciona.
El principio que lo resume todo
"Write tests. Not too many. Mostly integration." — Guillermo Rauch / Kent C. Dodds
Tres instrucciones en ocho palabras:
- Write tests — sí, los tests salvan tiempo y ahorran llamadas a las 2am
- Not too many — los rendimientos decrecen pasado cierto punto; 100% de coverage no es el objetivo
- Mostly integration — esta es la inversión con mejor ROI: prueban comportamiento real, resisten refactors, son más rápidos que E2E y más confiables que unit tests aislados
Referencias
- The Testing Trophy and Testing Classifications — Kent C. Dodds
- Write tests. Not too many. Mostly integration. — Kent C. Dodds
- Static vs Unit vs Integration vs E2E Testing for Frontend Apps — Kent C. Dodds
- Does the testing trophy need updating for 2025? — Call Kent Podcast
- Test Pyramid vs Testing Trophy: What Actually Works in Production — Ankur M
- bun-testing — tooling y configuración de tests en Wiedii (bun:test, mocks, fast-check)
- linters — análisis estático (capa base del trofeo) — TypeScript + ESLint