Continuando con el segundo artículo de la serie con título Kotlin «SpringBoot, Docker y DockerCompose II: test unitarios» en el cual me centraré en cómo definir pruebas unitarias. En la anterior entrada, «Kotlin, SpringBoot, Docker y DockerCompose I», realicé una descripción de una aplicación básica de ejemplo.
La serie está compuesta de tres entradas ordenadas de forma cronológica:
- «Kotlin, SpringBoot, Docker y DockerCompose I».- descripción del funcionamiento de la aplicación y los elementos básicos de funcionamiento.
- «Kotlin, SpringBoot, Docker y DockerCompose II: test unitarios».- descripción de cómo se realizan test unitarios en SpringBoot con Kotlin.
- «Kotlin, SpringBoot, Docker y DockerCompose III: docker y docker-compose».-descripción de la dockerización de la aplicación con Docker u DockerCompse.
La estructura del artículo está compuesto por los siguientes puntos:
- Test unitarios de controladores
- Test unitarios de servicios
- Test unitarios de repositorios
Para la realización de pruebas unitarias es necesario definir un conjunto de dependencias en el fichero de configuración de Gradle build.gradle.kts. Las dependencias necesarias son las siguientes:
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.security:spring-security-config") testImplementation("org.springframework.security:spring-security-test") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0")
Para realizar las pruebas unitarias es necesario tener definido el fichero con las propiedades para el entorno de pruebas; en el ejemplo, el contenido necesario del fichero application.properties es el siguiente:
spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password= kotlindocker.title=ExampleKotlinDocker kotlindocker.banner.title=Warning kotlindocker.banner.content=Kotlin application witth docker.
Las pruebas unitarias consiste en realizar test sobre un componente determinado. Si el componente tiene relaciones de asociación, o bien, usa elementos con una funcionalidad concreta, se puede declarar su resultado para realizar la prueba; estos casos, se emplean objetos mock para dichas pruebas.
1.- Test unitarios de controladores
Para definir una prueba unitaria de un controlador, en nuestro caso AppController, definiremos una clase de Test. La clase se test -AppControllerTest- debe de estar declarada con las siguientes anotaciones: @RunWith, @AutoConfigureMockMvc y @SpringBootTest.
La clase AppControllerTest dede tener definida la inyección de las entidades WebApplicationContext y MockMvc. Además, se definen la declaración de los elementos necesarios para moquear, en nuestro caso, la clase BusinessService mediante la anotación @MockBean.
La definición de un test se realiza definiendo funciones con la anotación @Test. En el cuerpo de la función, se declara el comportamiento del objeto moquedo, se lanza la petición al objeto a probar, en nuestro caso, una petición HTTP al enppoint /business/operation1 con los datos necesarios; y, para finalizar, se realiza el chequeo del resultado con las funciones del objeto MarcherAssert. El snippet del código es el siguiente:
@RunWith(SpringRunner::class) @AutoConfigureMockMvc @SpringBootTest class AppControllerTest { @Autowired private lateinit var webApplicationContext: WebApplicationContext @Autowired private lateinit var mockMvc: MockMvc @MockBean private lateinit var businessService: BusinessService @org.junit.Before fun setup() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .build() } @Test fun `AppControllerTest operation1`(){ val responseOperation1 = BusinessServiceResponse(message = "MessageTest") whenever(businessService.operationBusiness1( any() )).thenReturn( responseOperation1 ) val result = mockMvc.perform( MockMvcRequestBuilders.get("/business/operation1").accept(MediaType.APPLICATION_JSON) ) .andExpect(MockMvcResultMatchers.status().isOk) .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) .andReturn() MatcherAssert.assertThat(result.response.status, CoreMatchers.`is`(HttpStatus.OK.value())) verify(businessService).operationBusiness1( any() ) } }
2.- Test unitarios de servicios
La declaración de las pruebas unitarias de un servicio de negocio, sigue la misma línea que el de un controlador. En el ejemplo, definiré una prueba unitaria de la operación definida en el
servicio de negocio.
La diferencia radica en lo siguiente: los elementos que deben de ser moquedos, se declaran con la anotación @Mock; y, para el elemento a probar, se emplea la anotación @InjectMocks. La definición de la clase de test está definida exclusivamente con la anotación @RunWith(MockitoJUnitRunner::class). El snippet de la clase es el siguiente:
@RunWith(MockitoJUnitRunner::class) class BusinessServiceTest { @Mock private lateinit var authorRepository: AuthorRepository @InjectMocks private lateinit var businessService: BusinessService @Test fun `operation1`(){ val authorMock = Author(login = "loginTest", firstname = "firstNameTest", lastname = "lastnameTest") whenever(authorRepository.findByLogin( any() )).thenReturn( authorMock ) val request = BusinessServiceRequest(login = "Param1") val result = businessService.operationBusiness1(request) MatcherAssert.assertThat(result.message, CoreMatchers.`is`( "Param1-" + authorMock.login )) verify(authorRepository).findByLogin( any() ) } }
3.- Test unitarios de repositorios
La definición de las clases de test de un repositorio deben de tener la anotación @DataJpaTest y, como atributo, la definición de inyección de dependecia de los repositorios a probar. En nuestro caso, definiremos la referencia al repositorio ArticleRepository y, por otro lado, la referencia al EntityManager de test. La prueba de una operación se define en una función creando los datos de entrada, consulta al repositorio y verificación del resultado. El snippet con el código de ejemplo es el siguiente:
@DataJpaTest class ArticleRepositoryTest { @Autowired private lateinit var entityManager: TestEntityManager @Autowired private lateinit var articleRepository: ArticleRepository @Test fun `When findByIdOrNull then return Article`() { val juergen = Author("loginUserTest", "firstnameTest", "lastnameTest") val juergenInserted = entityManager.persist(juergen) assertThat(juergenInserted).isNotNull val article = Article("titleTest", "headLineTest", "ContentTest", juergen) val articleInserted = entityManager.persist(article) assertThat(articleInserted).isNotNull entityManager.flush() val found = articleRepository.findByIdOrNull(article.id!!) assertThat(found).isEqualTo(article) } }
Para el lector interesado en el código del proyeceto puede acceder al siguiente enlace
En la siguiente entrada, «Kotlin, SpringBoot, Docker y DockerCompose III: docker y docker-compose», describiré como realizar test unitarios de los elementos de la aplicación.