Testes de Integração de API REST: Escrita

Ao finalizarmos esta aula, entenderemos como escrever testes de integração expressivos, robustos e fáceis de manter para uma API REST que grava dados no banco de dados.

[!NOTE] Esta aula assume que:

  • Você sabe escrever testes de integração para aplicações web Spring com Kotlin 2.4.21 e JUnit Jupiter 6.0.3.
  • Você sabe criar uma classe request builder que encapsula o envio de requisições HTTP para o sistema sob teste.
  • Você sabe escrever testes unitários para funções de controller que gravam dados no banco de dados.
  • Você sabe configurar o sistema sob teste ao escrever testes de integração para uma API REST com Kotlin e JUnit Jupiter (utilizando a anotação composta @WebIntegrationTest).

1. Introdução ao Sistema Sob Teste

Durante esta aula, escreveremos testes de integração para uma função de controller que processa requisições HTTP POST enviadas para o caminho /api/task. Essa função insere uma nova tarefa no banco de dados PostgreSQL e retorna as informações da tarefa criada no formato JSON.

Quando o sistema sob teste salva uma nova tarefa no banco de dados, ele executa rigorosamente os seguintes passos:

  1. Lê as informações da tarefa criada a partir do corpo da requisição (@RequestBody) e valida os dados de entrada (@Valid). Se a validação falhar, retorna o código de status HTTP 400 Bad Request e devolve os erros de validação formatados em JSON.
  2. Obtém o usuário autenticado (um objeto UserDetails / LoggedInUser) a partir do contexto de segurança do Spring Security.
  3. Salva a nova tarefa no banco de dados.
  4. Retorna o código de status HTTP 201 Created e devolve as informações da tarefa criada como JSON.

O código-fonte da classe do nosso controller é implementado da seguinte forma com Spring Boot 4.1.2 e Kotlin 2.4.21:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.testwithspring.master.task

import jakarta.validation.Valid
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/task")
class TaskCrudController(
    @Autowired private val service: TaskCrudService
) {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    fun create(
        @RequestBody @Valid input: CreateTaskDTO,
        @AuthenticationPrincipal loggedInUser: LoggedInUser
    ): TaskDTO {
        return service.create(input, loggedInUser)
    }
}

[!NOTE] Os erros de validação são interceptados e processados globalmente pela classe TaskErrorHandler (anotada com @RestControllerAdvice). Embora a implementação desse handler não faça parte do escopo desta aula, é muito útil saber qual estrutura de documento JSON é devolvida ao cliente quando ocorrem violações de validação. Por exemplo, se o cliente tentar criar uma nova tarefa sem informar o título, o seguinte documento JSON será retornado:

1
2
3
4
5
6
7
8
{
    "fieldErrors": [
        {
            "field": "title",
            "errorCode": "NotBlank"
        }
    ]
}

A classe CreateTaskDTO transporta as informações necessárias para a criação da tarefa e especifica as regras de Jakarta Bean Validation aplicadas aos dados de entrada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.testwithspring.master.task

import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size

data class CreateTaskDTO(
    @field:NotBlank
    @field:Size(max = 100)
    val title: String?,

    @field:Size(max = 500)
    val description: String?
)

Após o sistema sob teste inserir com sucesso a nova tarefa no banco de dados, ele retorna o seguinte documento JSON de volta ao cliente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "creator": {
    "name": "John Doe",
    "userId": 1
  },
  "description": "This lesson talks about integration testing",
  "id": 1,
  "modifier": {
    "name": "John Doe",
    "userId": 1
  },
  "status": "OPEN",
  "tags": [],
  "title": "Write lesson"
}

Para simplificar a montagem das requisições HTTP nos testes, utilizamos a classe auxiliar TaskCrudRequestBuilder, que isola o uso de MockMvc e serialização JSON via ObjectMapper:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.testwithspring.master.task

import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post

class TaskCrudRequestBuilder(private val mockMvc: MockMvc) {

    private val objectMapper = ObjectMapper()

    fun create(input: CreateTaskDTO): ResultActions {
        return mockMvc.perform(
            post("/api/task")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsBytes(input))
        )
    }
}

[!TIP] No Spring Boot 4.1.2 com Kotlin, também é possível expressar chamadas de teste diretamente com a MockMvc Kotlin DSL:

1
2
3
4
5
fun createWithDsl(input: CreateTaskDTO) =
    mockMvc.post("/api/task") {
        contentType = MediaType.APPLICATION_JSON
        content = objectMapper.writeValueAsString(input)
    }

O padrão request builder traz a vantagem adicional de encapsular URLs e serializações em um único componente reutilizável em dezenas de testes.

A seguir, configuraremos o sistema sob teste.


2. Configurando o Sistema Sob Teste

Durante esta aula, escreveremos testes de integração que verificam se o sistema sob teste funciona conforme o esperado quando um usuário comum autenticado fornece informações válidas. Podemos configurar o sistema sob teste seguindo estes passos:

Primeiro, precisamos adicionar uma constante à nossa classe de teste. Essa constante especifica o ID esperado para a tarefa criada (NEXT_FREE_ID = 1L). Depois de adicionarmos essa constante, o código-fonte da classe de teste fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package com.testwithspring.master.task

import com.testwithspring.master.common.WebIntegrationTest
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)
}

Segundo, precisamos adicionar uma propriedade privada somente leitura à nossa classe de teste. Essa propriedade contém um objeto IdColumnReset capaz de reiniciar as sequências ou colunas de auto-incremento das tabelas do banco de dados antes da execução de cada teste. Podemos adicionar essa propriedade seguindo dois passos:

  1. Adicionar uma propriedade privada somente leitura chamada jdbcTemplate à nossa classe de teste e injetar o seu valor usando injeção via construtor (@Autowired). Essa propriedade armazena uma instância de NamedParameterJdbcTemplate que nos permite manipular sequências no PostgreSQL.
  2. Adicionar a propriedade idColumnReset instanciada a partir do jdbcTemplate.

Depois de adicionarmos essas propriedades, o código-fonte da classe de teste fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.testwithspring.master.task

import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)
}

A classe utilitária IdColumnReset executa o comando SQL apropriado para reiniciar a sequência da tabela (tasks_id_seq) no PostgreSQL (executado sob Testcontainers 2.0.5):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.testwithspring.master.common

import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate

class IdColumnReset(
    private val jdbcTemplate: NamedParameterJdbcTemplate
) {

    companion object {
        private const val QUERY_RESET_ID_COLUMN_TEMPLATE =
            "ALTER SEQUENCE %s RESTART WITH 1"
    }

    fun resetIdColumns(vararg tableNames: String) {
        tableNames.forEach { tableName ->
            val query = String.format(QUERY_RESET_ID_COLUMN_TEMPLATE, "${tableName}_id_seq")
            jdbcTemplate.update(query, emptyMap<String, Any>())
        }
    }
}

Terceiro, precisamos adicionar uma propriedade privada somente leitura à nossa classe de teste contendo os dados de entrada (INPUT) fornecidos ao sistema sob teste. Depois de adicionarmos essa propriedade, o código da classe fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.testwithspring.master.task

import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )
}

A classe Tasks é uma classe de constantes compartilhada que centraliza os dados de teste utilizados nos nossos testes de integração:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.testwithspring.master.task

class Tasks {

    class WriteLesson {

        companion object {
            const val ID = 2L
            const val CREATOR_ID = 1L
            const val DESCRIPTION = "This lesson talks about integration testing"
            const val TITLE = "Write lesson"
            val STATUS = TaskStatus.OPEN
        }
    }
}

Quarto, precisamos escrever uma função de configuração anotada com @BeforeEach que seja executada antes de cada método de teste. A implementação dessa função deve reiniciar a sequência da coluna identificadora da tabela tasks no banco de dados. Após adicionarmos essa função, a classe de teste fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.testwithspring.master.task

import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }
}

Quinto, precisamos criar a classe interna aninhada (inner class) anotada com @Nested que conterá as funções de teste responsáveis por validar o comportamento do sistema sob teste quando um usuário comum (John Doe) fornece informações válidas.

Após criarmos essa classe interna, a estrutura da classe de teste fica da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

    }
}

O dataset do DbUnit (users.xml) que popula os dados de usuários no banco de dados tem a seguinte estrutura:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<dataset>
    <user_accounts id="1"
                   email_address="john.doe@gmail.com"
                   is_enabled="true"
                   name="John Doe"
                   password="user"
                   role="ROLE_USER"/>
    <user_accounts id="2"
                   email_address="anne.admin@gmail.com"
                   is_enabled="true"
                   name="Anne Admin"
                   password="admin"
                   role="ROLE_ADMIN"/>
</dataset>

O dataset do DbUnit (no-tasks-and-tags.xml) que assegura que as tabelas de tarefas e tags estejam inicialmente limpas possui o seguinte conteúdo:

1
2
3
4
5
<dataset>
    <tasks/>
    <tags/>
    <tasks_tags/>
</dataset>

A classe Users é uma classe de constantes que armazena os dados de teste encontrados na tabela user_accounts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.testwithspring.master.user

class Users {

    class JohnDoe {

        companion object {
            const val ID = 1L
            const val EMAIL_ADDRESS = "john.doe@gmail.com"
            const val NAME = "John Doe"
        }
    }
}

[!NOTE] A aplicação de exemplo completa deste tópico também contém testes de integração que asseguram o funcionamento esperado do sistema sob teste quando o usuário é anônimo (garantindo que o Spring Security retorne HTTP 401 Unauthorized) ou quando o usuário autenticado possui o perfil de administrador (ROLE_ADMIN).

Agora que concluímos toda a infraestrutura e configuração do sistema sob teste, avançaremos para a escrita dos testes de integração da função do controller.


3. Escrevendo Testes de Integração para a Nossa Função de Controller

Ao testarmos operações de escrita em uma API REST, devemos validar duas dimensões essenciais:

  1. Persistência no Banco de Dados: Assegurar que os registros inseridos na tabela tasks contenham exatamente os valores esperados para cada coluna, utilizando datasets verificadores do DbUnit com a anotação @ExpectedDatabase e o modo DatabaseAssertionMode.NON_STRICT.
  2. Resposta HTTP ao Cliente: Assegurar que o endpoint retorne o status HTTP correto, os cabeçalhos apropriados e o payload JSON com todos os campos mapeados corretamente.

Seguindo uma estratégia de testes limpos e focados, cada método de teste valida exatamente um único conceito lógico. Podemos implementar nossa suíte de testes seguindo estes passos:

Passo 1: Verificar se o sistema sob teste utiliza o próximo ID livre no banco de dados

Primeiro, precisamos verificar se o sistema sob teste utiliza o próximo ID disponível quando insere uma nova tarefa no banco de dados.

Adicionamos a função shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() à classe aninhada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }
    }
}

O dataset esperado do DbUnit (create-task-id-expected.xml) garante que a nova linha gravada na tabela tasks possua o id="1":

1
2
3
<dataset>
    <tasks id="1"/>
</dataset>

Passo 2: Garantir que o sistema sob teste insira os valores corretos das colunas de ciclo de vida no banco de dados

Segundo, precisamos garantir que o sistema sob teste insira corretamente as colunas de auditoria e ciclo de vida (assignee_id, closer_id, creator_id e modifier_id) na tabela de tarefas. Como o usuário autenticado é John Doe (id=1) e uma nova tarefa nasce sem responsável nem finalizador, esperamos creator_id="1", modifier_id="1", assignee_id="[null]" e closer_id="[null]".

Após adicionarmos a função shouldInsertCorrectLifecycleColumnValuesIntoDatabase(), a classe de teste fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }
    }
}

O dataset esperado do DbUnit (create-task-lifecycle-columns-johndoe-expected.xml) valida exatamente esse estado:

1
2
3
4
5
6
<dataset>
    <tasks assignee_id="[null]"
           closer_id="[null]"
           creator_id="1"
           modifier_id="1"/>
</dataset>

Passo 3: Verificar se o sistema sob teste salva o status e a resolução corretos no banco de dados

Terceiro, precisamos verificar se o sistema sob teste salva a tarefa com o status inicial OPEN e resolução nula (resolution="[null]").

Após adicionarmos a função shouldSaveCorrectStatusAndResolutionToDatabase(), a classe de teste fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }
    }
}

O dataset esperado do DbUnit (create-task-status-expected.xml) valida os campos de status:

1
2
3
4
<dataset>
    <tasks resolution="[null]"
           status="OPEN"/>
</dataset>

Passo 4: Garantir que o sistema sob teste insira o título e a descrição corretos no banco de dados

Quarto, precisamos garantir que o sistema sob teste insira as informações fornecidas no corpo da requisição (title e description) de maneira idêntica na tabela tasks.

Após adicionarmos a função shouldInsertCorrectTitleAndDescriptionToDatabase(), a classe de teste fica assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }
    }
}

O dataset esperado do DbUnit (create-task-title-description-expected.xml) valida o conteúdo dos dados textuais:

1
2
3
4
<dataset>
    <tasks description="This lesson talks about integration testing"
           title="Write lesson"/>
</dataset>

Passo 5: Verificar se o sistema sob teste cria uma tarefa que não possui tags no banco de dados

Quinto, precisamos verificar se o sistema sob teste não vincula tags espúrias à tarefa recém-criada, mantendo as tabelas tags e tasks_tags vazias.

Após adicionarmos a função shouldCreateTaskThatHasNoTags(), a classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }
    }
}

O dataset esperado do DbUnit (create-task-tags-expected.xml) assegura a ausência de registros de tags:

1
2
3
4
<dataset>
    <tags/>
    <tasks_tags/>
</dataset>

Passo 6: Garantir que o sistema sob teste retorne o código de status HTTP 201 (Created)

Sexto, passamos a verificar as respostas HTTP devolvidas pela API REST. O primeiro requisito é garantir que o endpoint responda com o código de status HTTP 201 Created.

Adicionamos a função shouldReturnHttpStatusCodeCreated() à nossa classe de teste:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }
    }
}

Passo 7: Garantir que o sistema sob teste retorne as informações da tarefa criada como JSON

Sétimo, precisamos garantir que o cabeçalho Content-Type da resposta HTTP seja application/json.

Após adicionarmos a função shouldReturnInformationOfCreatedTaskAsJson(), o código da classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }
    }
}

Passo 8: Verificar se o sistema sob teste retorna as informações da tarefa com o ID correto

Oitavo, precisamos verificar se o campo $.id retornado no JSON da resposta possui exatamente o valor numérico gerado (NEXT_FREE_ID.toInt()).

Após adicionarmos a função shouldReturnInformationOfCreatedTask(), o código da classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }
    }
}

Passo 9: Garantir que o sistema sob teste retorne uma tarefa sem responsável atribuído

Nono, precisamos assegurar que a tarefa recém-criada não tenha nenhum responsável no payload retornado ($.assignee deve ser nulo).

Após adicionarmos a função shouldReturnTaskThatHasNoAssignee(), o código da classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }
    }
}

Passo 10: Verificar se o sistema sob teste retorna uma tarefa sem finalizador

Décimo, precisamos verificar se a tarefa retornada não possui finalizador ($.closer deve ser nulo).

Após adicionarmos a função shouldReturnTaskThatHasNoCloser(), a classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }
    }
}

Passo 11: Garantir que o sistema sob teste retorne as informações da pessoa que criou a tarefa

Décimo Primeiro, precisamos garantir que o sistema sob teste retorne as informações do criador da tarefa nos campos $.creator.name e $.creator.userId, correspondendo aos dados de John Doe.

Após adicionarmos a função shouldReturnCorrectCreator(), o código da classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }

        @Test
        @DisplayName("Should return the the information of the person who created the task")
        fun shouldReturnCorrectCreator() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.creator.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.creator.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }
    }
}

Passo 12: Garantir que o sistema sob teste retorne as informações de quem modificou a tarefa

Décimo Segundo, precisamos garantir que o sistema sob teste retorne as informações de quem modificou a tarefa ($.modifier.name e $.modifier.userId), que na criação inicial também apontam para John Doe.

Após adicionarmos a função shouldReturnCorrectModifier(), a classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }

        @Test
        @DisplayName("Should return the the information of the person who created the task")
        fun shouldReturnCorrectCreator() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.creator.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.creator.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the the information of the person who modified the task")
        fun shouldReturnCorrectModifier() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.modifier.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.modifier.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }
    }
}

Passo 13: Verificar se o sistema sob teste retorna a descrição e o título corretos no corpo da resposta

Décimo Terceiro, precisamos verificar se o payload JSON retornado reflete fielmente os valores de title e description enviados na requisição.

Após adicionarmos a função shouldReturnCorrectDescriptionAndTitle(), a classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }

        @Test
        @DisplayName("Should return the the information of the person who created the task")
        fun shouldReturnCorrectCreator() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.creator.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.creator.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the the information of the person who modified the task")
        fun shouldReturnCorrectModifier() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.modifier.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.modifier.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the correct description and title")
        fun shouldReturnCorrectDescriptionAndTitle() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.description", equalTo(Tasks.WriteLesson.DESCRIPTION)))
                .andExpect(jsonPath("$.title", equalTo(Tasks.WriteLesson.TITLE)))
        }
    }
}

Passo 14: Garantir que o sistema sob teste retorne uma tarefa em aberto

Décimo Quarto, precisamos garantir que o sistema sob teste retorne uma tarefa com status igual a "OPEN" e resolution nula no corpo da resposta JSON.

Após adicionarmos a função shouldReturnOpenTask(), a classe de teste fica assim:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }

        @Test
        @DisplayName("Should return the the information of the person who created the task")
        fun shouldReturnCorrectCreator() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.creator.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.creator.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the the information of the person who modified the task")
        fun shouldReturnCorrectModifier() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.modifier.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.modifier.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the correct description and title")
        fun shouldReturnCorrectDescriptionAndTitle() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.description", equalTo(Tasks.WriteLesson.DESCRIPTION)))
                .andExpect(jsonPath("$.title", equalTo(Tasks.WriteLesson.TITLE)))
        }

        @Test
        @DisplayName("Should return an open task")
        fun shouldReturnOpenTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.resolution", nullValue()))
                .andExpect(jsonPath("$.status", equalTo(Tasks.WriteLesson.STATUS.name)))
        }
    }
}

Passo 15: Verificar se o sistema sob teste retorna uma tarefa que não possui tags

Décimo Quinto, precisamos verificar se o array $.tags retornado no JSON da resposta é vazio (tamanho 0).

Adicionamos a função shouldReturnTaskThatHasNoTags() à nossa suíte, consolidando a classe de teste final:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package com.testwithspring.master.task

import com.github.springtestdbunit.annotation.DatabaseSetup
import com.github.springtestdbunit.annotation.DbUnitConfiguration
import com.github.springtestdbunit.annotation.ExpectedDatabase
import com.github.springtestdbunit.assertion.DatabaseAssertionMode
import com.github.springtestdbunit.dataset.ReplacementDataSetLoader
import com.testwithspring.master.common.IdColumnReset
import com.testwithspring.master.common.WebIntegrationTest
import com.testwithspring.master.user.Users
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.security.test.context.support.WithUserDetails
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@WebIntegrationTest
@DisplayName("Create a new task by using valid information")
class CreateNewTaskWithValidInformationApiTest(
    @Autowired private val jdbcTemplate: NamedParameterJdbcTemplate,
    @Autowired private val mockMvc: MockMvc
) {

    companion object {
        private const val NEXT_FREE_ID = 1L
    }

    private val idColumnReset = IdColumnReset(jdbcTemplate)
    private val requestBuilder = TaskCrudRequestBuilder(mockMvc)

    private val INPUT = CreateTaskDTO(
        description = Tasks.WriteLesson.DESCRIPTION,
        title = Tasks.WriteLesson.TITLE
    )

    @BeforeEach
    fun resetIdColumn() {
        idColumnReset.resetIdColumns("tasks")
    }

    @Nested
    @WebIntegrationTest
    @DatabaseSetup(value = [
        "/com/testwithspring/master/users.xml",
        "/com/testwithspring/master/no-tasks-and-tags.xml"
    ])
    @DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader::class)
    @WithUserDetails(Users.JohnDoe.EMAIL_ADDRESS)
    @DisplayName("When the user is John Doe")
    inner class WhenUserIsJohnDoe {

        @Test
        @DisplayName("Should use the next free id when a new task is saved to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-id-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldUseNextFreeIdWhenNewTaskIsSavedToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert correct lifecycle column values into the database")
        @ExpectedDatabase(
            value = "create-task-lifecycle-columns-johndoe-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectLifecycleColumnValuesIntoDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should save the correct status and resolution to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-status-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldSaveCorrectStatusAndResolutionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should insert the correct title and description to the database")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-title-description-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldInsertCorrectTitleAndDescriptionToDatabase() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should create a task that has no tags")
        @ExpectedDatabase(
            value = "/com/testwithspring/master/task/create-task-tags-expected.xml",
            assertionMode = DatabaseAssertionMode.NON_STRICT
        )
        fun shouldCreateTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
        }

        @Test
        @DisplayName("Should return the HTTP status code created")
        fun shouldReturnHttpStatusCodeCreated() {
            requestBuilder.create(INPUT)
                .andExpect(status().isCreated)
        }

        @Test
        @DisplayName("Should return the information of the created task as Json")
        fun shouldReturnInformationOfCreatedTaskAsJson() {
            requestBuilder.create(INPUT)
                .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        }

        @Test
        @DisplayName("Should return the information of the created task")
        fun shouldReturnInformationOfCreatedTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.id", equalTo(NEXT_FREE_ID.toInt())))
        }

        @Test
        @DisplayName("Should return a task that has no assignee")
        fun shouldReturnTaskThatHasNoAssignee() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.assignee", nullValue()))
        }

        @Test
        @DisplayName("Should return a task that has no closer")
        fun shouldReturnTaskThatHasNoCloser() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.closer", nullValue()))
        }

        @Test
        @DisplayName("Should return the the information of the person who created the task")
        fun shouldReturnCorrectCreator() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.creator.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.creator.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the the information of the person who modified the task")
        fun shouldReturnCorrectModifier() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.modifier.name", equalTo(Users.JohnDoe.NAME)))
                .andExpect(jsonPath("$.modifier.userId", equalTo(Users.JohnDoe.ID.toInt())))
        }

        @Test
        @DisplayName("Should return the correct description and title")
        fun shouldReturnCorrectDescriptionAndTitle() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.description", equalTo(Tasks.WriteLesson.DESCRIPTION)))
                .andExpect(jsonPath("$.title", equalTo(Tasks.WriteLesson.TITLE)))
        }

        @Test
        @DisplayName("Should return an open task")
        fun shouldReturnOpenTask() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.resolution", nullValue()))
                .andExpect(jsonPath("$.status", equalTo(Tasks.WriteLesson.STATUS.name)))
        }

        @Test
        @DisplayName("Should return a task that has no tags")
        fun shouldReturnTaskThatHasNoTags() {
            requestBuilder.create(INPUT)
                .andExpect(jsonPath("$.tags", hasSize<Any>(0)))
        }
    }
}

Agora que implementamos todos os métodos de teste cobrindo tanto a persistência quanto a resposta HTTP, vamos resumir as lições essenciais aprendidas neste capítulo.


4. Resumo

Esta aula nos ensinou duas lições fundamentais para a criação de testes de integração robustos em APIs REST de escrita:

  1. Reinicialização Determinística de Chaves Primárias: Podemos resetar as sequências auto-incremento do PostgreSQL (executado via Testcontainers 2.0.5) utilizando NamedParameterJdbcTemplate e a classe utilitária IdColumnReset disparada no @BeforeEach. Isso garante que cada teste execute em um estado perfeitamente previsível, permitindo asserções exatas sobre valores gerados como NEXT_FREE_ID = 1L.
  2. Asserções Focadas com Datasets Pequenos do DbUnit: Ao utilizarmos múltiplos datasets pequenos do DbUnit configurados com DatabaseAssertionMode.NON_STRICT, conseguimos escrever métodos de teste que validam exatamente um único conceito lógico (como verificação de ID, colunas de ciclo de vida, status/resolução ou ausência de relacionamentos), desacoplando a verificação de integridade no banco de dados da validação de payloads e códigos HTTP devolvidos pelo Spring MVC / MockMvc.