Testando construcción condicional de beans con Spring Boot
15-04-2020
¿Cómo elegir la implementación concreta de una interfaz mediante un fichero de configuración?
En este proyecto, tenían una interfaz para un servicio y dos implementaciones. La implementación final se elige según un valor en la configuración del proyecto, de forma que es posible desplegar la app en varios servidores y elegir en cada uno, una opción diferente.
El código existente era similar a este (lo he simplificado un poco para el ejemplo):
public interface ProductService {
List<Product> findAll();
}
@Service
@ConditionalOnProperty(prefix = "product-service", name = "type", havingValue = "db")
public class DbProductService implements ProductService {
public List<Product> findAll() {
return productRepository.findAll();
}
}
@Service
@ConditionalOnProperty(prefix = "product-service", name = "type", havingValue = "file")
public class FileProductService implements ProductService {
public List<Product> findAll() {
return productSpreadSheet.findAll();
}
}
Luego, en el archivo de configuración (application.yml
) de cada destino, se especifica el tipo de servicio que se
necesita:
product-service: type: file
Para mi tarea solo necesitaba crear una nueva implementación que no retornara nada. De forma que pudiera desactivar ese servicio, sin necesidad de modificar otras partes del código. Una variación simplificada del patron Null Object, algo así sería suficiente:
@Service
@ConditionalOnProperty(prefix = "product-service", name = "type", havingValue = "none")
public class NullProductService {
public List<Product> findAll() {
return List.of();
}
}
{{< /highlight >}}
## Testeando servicios condicionales
Spring tiene una forma de testear este tipo de componentes condicionales. Por si algún día lo necesitas, se puede hacer
con algo así:
class ProductServiceTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(
NullProductService.class,
DbProductService.class,
FileProductService.class
);
@Test
void loadsNullProductServiceWhenTypeIsNone() {
contextRunner
.withPropertyValues("product-service.type=none")
.run(context -> assertAll(
() -> assertThat(context).hasSingleBean(NullProductService.class),
() -> assertThat(context).doesNotHaveBean(DbProductService.class),
() -> assertThat(context).doesNotHaveBean(FileProductService.class)
));
}
}
Este test, en lugar de arrancar toda la aplicación, solo crea un `ApplicationContext` con la información indicada, haciendo que se ejecute mucho más rápido.
Puesto que la inyección de dependencias la gestiona Spring Boot, puede que en determinados momentos como en este, queramos tener tests que se aseguran de que todo está configurado como esperamos. Esta es una manera de hacerlos.