leanmind logo leanmind text logo

Blog

Código Sostenible

Cómo escribir código fácil de mantener mediante valores, principios y técnicas.

Testando construcción condicional de beans con Spring Boot

Por José Luis Rodríguez Alonso

¿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):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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:

1
2
3
4
5
6
7
@Service
@ConditionalOnProperty(prefix = "product-service", name = "type", havingValue = "none")
public class NullProductService {
	public List<Product> findAll() {
		return List.of();
	}
}

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í:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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.

Publicado el 15/04/2020 por
José Luis image

José Luis Rodríguez Alonso

¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.

Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?

Impulsamos el crecimiento profesional de tu equipo de developers