Kotlin, SpringBoot, Docker y DockerCompose II: test unitarios

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:

La estructura del artículo está compuesto por los siguientes puntos:

  1. Test unitarios de controladores
  2. Test unitarios de servicios
  3. 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.

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s