Dobles de Tests
Al igual que en el cine para las escenas peligrosas se buscan dobles de los protagonistas, cuando escribimos tests para eliminar efectos no deseados se utilizan dobles.

Utilizamos dobles de test cuando sobre una funcionalidad necesitamos la colaboración de otras entidades y estas otras entidades son muy complejas y nos obligarían a crear multitud de tests o producen efectos secundarios, guardan datos en una base de datos o llaman a otros servicios externos.
Los dobles de test se comportarán como se debiera de comportar la entidad real, pero solo en lo que concierne al comportamiento que estamos testeando. Todo lo demás no es importante para nosotros. Se suelen utilizar en más medida cuando hacemos test unitarios, pero perfectamente se pueden usar en otro tipo de tests (integración o e2e).
En el mundo de los tests hay dos grandes corrientes, relacionadas básicamente con el TDD:
- Los tests solitarios, donde la unidad está mucho más relacionada con la unidad del lenguaje de programación (clases, funciones, módulos, etc). Se suelen utilizar mucho los dobles de tests entre diferentes unidades del lenguaje para controlar comportamientos
- Los tests sociables, donde la unidad es el comportamiento a testear y se suele limitar mucho el uso de los dobles de tests. Mucha gente aboga últimamente por crear muchos tests de integración y tener un trofeo en vez de una pirámide de tests (con la base en los unitarios).
Mi opinión está mucho más cerca de los tests sociables, pero sin caer en lo que yo creo que es un error, más tests de integración que unitarios. Si buscáis una explicación más amplia aquí la tenéis, unit tests.
¿Cuándo usar un doble de tests?
Yo suelo hacer TDD, así que cuando entiendo que una funcionalidad sobrepasa los límites de la entidad que estoy construyendo (no tiene relación con la capa que estoy creando por ejemplo), creo una interfaz y genero un test doble para ella.
Esto me permite construir a partir de las entidades más abstractas (diseño descendente) sin prestar mucha atención en los detalles de las más específicas. Básicamente es aplicar el principio de Dependency Inversion de SOLID.
Por poner un ejemplo en una aplicación rest, podría construir el controlador creando un test double para el servicio que hará la lógica de mi aplicación. Así se puede testear que el controlador hace lo que se espera de él, unir el mundo de la vista (json, http, etc) con el mundo de la lógica de mi negocio.
Puedo entonces crear un test para mi controlador:
- Que sea capaz de levantar un servidor http.
- Que pueda responder a peticiones get, post, put lo que sea en la url que yo decida.
- Que me devuelva un objeto en el formato que sea (json, html, xml) cuando le pase unos parámetros específicos o un body en la petición que le haga.
Los detalles de implementación de mi servicio en el momento de crear el controlador son un poco irrelevantes con lo que es un gran candidato para ser sustituido por un doble de test. Básicamente esta decisión me está marcando una separación entre dos mundos, el mundo de la infraestructura relacionado con http y el de mi lógica de negocio mi dominio. Estoy creando capas en mi diseño, es aplicar el principio de inversión de dependencias, es la raíz de la Arquitectura Hexagonal.
Luego en esta aproximación nos damos cuenta de que los dobles de tests me permiten definir los límites de mis capas. Y nos dan otra gran oportunidad, la de definir lo que nuestra siguiente capa debe hacer. Con unos parámetros de entrada nuestro test doble devuelve una respuesta prefijada. Este hecho representa un contrato entre el test doble y lo que el código real debe hacer.
Cada una de las asunciones hechas en mis test dobles deben representar un test de mi servicio real, así aplicando la regla de la cadena tendremos todo creado con tests a diferentes niveles.
Si nuestros servicios de dominio necesitan crear algún side effect, escribir en una base de datos por ejemplo. Ahí tenemos otra oportunidad para aplicar la inversión de dependencias, no debemos basarnos en detalles de cómo la base de datos funciona o que nuestras entidades, objetos funciones dependan de ella. Creemos otra interfaz y otro doble de test para trabajar sobre esos detalles después (last responsible moment).
Tipos de dobles de tests
Dummy: estos se usan para cumplir la signatura de un método, pero en realidad nuestra funcionalidad no depende de ellos, no se usan o lo hacen para recuperar algún atributo que contienen. El ejemplo más simple es cuando testeamos una funcionalidad en un escenario en concreto donde hay parámetros que no se usan, podemos pasar nulos ahí si es que nuestro lenguaje lo permite (https://blog.thecodewhisperer.com/permalink/null-design-tool)
Fakes: son implementaciones alternativas más simples del objeto real, pero que no están listas para ir a producción porque no cumplen algún requisito no funcional de nuestro sistema, un ejemplo sería una base de datos en memoria. Los Fakes son muy interesantes para testear infraestructura, un controlador necesitará un servicio http para comprobar que realmente está escuchando, pero no necesariamente tiene que ser un servidor completo podemos usar alguna librería que cumpla, pero sea más rápida de cargar.
Stubs: Son entidades con respuestas preprogramadas para ciertas preguntas. En nuestro ejemplo anterior si el servicio nos debe devolver un objeto con ciertos valores R cuando le pasamos el input A, crearíamos un stub que hace eso mismo si le pasamos el input A devuelve R. Los stubs son muy útiles para reemplazar código que devuelve resultados, sin crear todavía ese código. Por ejemplo si hacemos hexagonal podemos usar stubs para reemplazar los repositorios (de hexagonal) que usamos.
Spyes: Los spyes son proxies sobre las entidades reales o sobre stubs para permitir verificar que se llamaron con unos parámetros. Los spyes pueden ser útiles cuando no es fácil reemplazar una implementación real, pero queremos comprobar que se llama de cierta manera.
Mocks: Son parecidos a los stubs, pero permiten verificar que fueron llamados con los parámetros correctos en el test. De alguna manera los mocks verifican el comportamiento que tiene nuestro servicio a testear con respecto a sus colaboradores. Los mocks permiten ser estrictos con el comportamiento que esperamos que ocurra con respecto a nuestros dobles de test, cómo se llamarán y con qué parámetros. Me parecen geniales para comprobar la ejecución de comandos donde no me interesa la respuesta de vuelta, métodos void.
Estos artículos son geniales para entender más sobre dobles de tests: