Ao concluirmos esta aula, nós:
- Seremos capazes de escrever asserções expressivas com JUnit Jupiter 6 e Hamcrest em Kotlin.
- Compreenderemos quando devemos utilizar Hamcrest (especialmente ao testar controladores Spring MVC com
MockMvc).
Configurando as Dependências Necessárias
Antes de podermos escrever asserções com o Hamcrest, precisamos garantir que a biblioteca esteja disponível no classpath de teste. No ecossistema moderno (Hamcrest 2.x / 3.x), o artefato unificado é o org.hamcrest:hamcrest (que inclusive já vem incluído transitivamente no spring-boot-starter-test).
Se estivermos usando Maven em um projeto isolado, declaramos a dependência no arquivo pom.xml:
1
2
3
4
5
6
|
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
|
No Gradle 9 (Kotlin DSL), declaramos a dependência dentro da nossa suíte de testes no arquivo build.gradle.kts:
1
2
3
4
5
6
7
8
9
10
|
testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter("6.0.3")
dependencies {
implementation("org.hamcrest:hamcrest:3.0")
}
}
}
}
|
Depois de adicionarmos essa dependência ao nosso script de build, podemos começar a escrever asserções com o Hamcrest.
Quando queremos escrever asserções com o Hamcrest no JUnit Jupiter 6, utilizamos o método estático assertThat() da classe org.hamcrest.MatcherAssert (que substituiu o antigo org.junit.Assert.assertThat depreciado no JUnit 4).
Esse método recebe até três parâmetros na seguinte ordem:
- Um parâmetro opcional (
String) contendo a mensagem de erro customizada que será exibida caso a asserção falhe.
- O valor ou objeto real obtido (
actual).
- O matcher do Hamcrest (
Matcher<T>) que especifica a condição ou valor esperado (expected).
Nota sobre a Palavra Reservada is no Kotlin:
Em Kotlin, is é uma palavra-chave reservada da linguagem (usada para checagem de tipos, equivalente ao instanceof do Java). Por isso, ao usar o matcher Matchers.is(...) do Hamcrest em Kotlin, você pode:
1. Envolver o método entre crases: `is`(expected);
2. Usar seu método equivalente no Hamcrest: equalTo(expected);
3. Ou usar um import alias do Kotlin no topo do arquivo: import org.hamcrest.Matchers.`is` as isEqualTo.
Vamos avançar e examinar exemplos práticos que demonstram como escrever diferentes tipos de asserções com Hamcrest em Kotlin.
Asserções para Valores Booleanos
Se quisermos verificar que um valor Boolean é true, criamos o matcher apropriado invocando o método `is`(true) da classe Matchers. Em outras palavras, podemos usar uma destas duas asserções:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test
class BooleanAssertionTest {
companion object {
private const val TRUE = true
}
@Test
fun booleanShouldBeTrue() {
assertThat(TRUE, `is`(true))
assertThat(
"Expected boolean to be true but was false",
TRUE,
`is`(true)
)
}
}
|
Se quisermos verificar que um valor Boolean é false, invocamos `is`(false) da classe Matchers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test
class BooleanAssertionTest {
companion object {
private const val FALSE = false
}
@Test
fun booleanShouldBeFalse() {
assertThat(FALSE, `is`(false))
assertThat(
"Expected boolean to be false but was true",
FALSE,
`is`(false)
)
}
}
|
Asserções para Verificar se um Objeto É null ou Não É null
Se quisermos verificar que um objeto é null, criamos o matcher do Hamcrest invocando o método nullValue() da classe Matchers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.Test
class ObjectAssertionTest {
@Test
fun objectShouldBeNull() {
val actual: Any? = null
assertThat(actual, nullValue())
assertThat(
"Expected object to be null but was: $actual",
actual,
nullValue()
)
}
}
|
Se quisermos verificar que um objeto não é null, criamos o matcher invocando o método notNullValue() da classe Matchers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.notNullValue
import org.junit.jupiter.api.Test
class ObjectAssertionTest {
@Test
fun objectShouldNotBeNull() {
val actual: Any? = Any()
assertThat(actual, notNullValue())
assertThat(
"Expected object to be not null but was null",
actual,
notNullValue()
)
}
}
|
Asserções de Igualdade entre Dois Valores ou Objetos
Se quisermos verificar que o valor (ou objeto) real obtido é igual ao valor (ou objeto) esperado, invocamos o método `is`() (ou equalTo()) da classe Matchers. Por exemplo, para comparar dois números inteiros (Int), usamos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test
class ObjectAssertionTest {
@Test
fun twoIntegersShouldBeEqual() {
val actual = 9
val expected = 9
assertThat(actual, `is`(expected))
assertThat(
"Expected integer to be: $expected but was: $actual",
actual,
`is`(expected)
)
}
}
|
Se quisermos verificar que o valor real obtido não é igual ao valor esperado, invocamos o método not() da classe Matchers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.junit.jupiter.api.Test
class ObjectAssertionTest {
@Test
fun twoIntegersShouldNotBeEqual() {
val actual = 9
val expected = 41
assertThat(actual, not(expected))
assertThat(
"Expected integer to not be: $expected but was: $actual",
actual,
not(expected)
)
}
}
|
Asserções de Referência de Objetos
Se quisermos verificar que duas variáveis referenciam exatamente a mesma instância de objeto na memória, criamos o matcher invocando o método sameInstance() da classe Matchers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.sameInstance
import org.junit.jupiter.api.Test
class ObjectAssertionTest {
@Test
fun shouldPointToSameObject() {
val actual = Any()
val expected = actual
assertThat(actual, sameInstance(expected))
assertThat(
"Expected two objects to be the same but they are not",
actual,
sameInstance(expected)
)
}
}
|
Nota: Para verificar que dois objetos não apontam para a mesma instância no Hamcrest, você pode combinar os matchers not(sameInstance(expected)).
Asserções de Igualdade entre Dois Arrays
Se quisermos verificar que dois arrays são iguais, podemos invocar o método `is`() da classe Matchers. Por exemplo, para verificar que dois IntArray possuem os mesmos elementos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test
class ArrayAssertionTest {
@Test
fun twoIntArraysShouldBeEqual() {
val actual = intArrayOf(2, 5, 7)
val expected = intArrayOf(2, 5, 7)
assertThat(actual, `is`(expected))
assertThat(
"Expected array: ${expected.contentToString()} but got array: ${actual.contentToString()}",
actual,
`is`(expected)
)
}
}
|
Asserções para Listas (List)
Se quisermos escrever uma asserção que verifica se o tamanho (size) de uma lista está correto, criamos o matcher invocando o método hasSize() da classe Matchers. Por exemplo, para verificar que o tamanho de uma lista vazia é 0:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.junit.jupiter.api.Test
class ListAssertionTest {
@Test
fun sizeOfEmptyListShouldBeZero() {
val emptyList = emptyList<String>()
assertThat(emptyList, hasSize(0))
assertThat(
"Expected size to be: 0 but was: ${emptyList.size}",
emptyList,
hasSize(0)
)
}
}
|
Se quisermos escrever uma asserção que verifica se uma lista contém os objetos esperados, escolhemos um destes dois matchers do Hamcrest:
- Para verificar que a lista contém os objetos esperados na ordem exata especificada, invocamos o método
contains() da classe Matchers.
- Para verificar que a lista contém os objetos esperados em qualquer ordem, invocamos o método
containsInAnyOrder() da classe Matchers.
A classe de teste que valida ambas as situações 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
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.containsInAnyOrder
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class ListAssertionTest {
private lateinit var first: Any
private lateinit var second: Any
private lateinit var list: List<Any>
@BeforeEach
fun createAndInitializeList() {
first = Any()
second = Any()
list = listOf(first, second)
}
@Test
fun listShouldContainCorrectObjectsInCorrectOrder() {
assertThat(list, contains(first, second))
}
@Test
fun listShouldContainCorrectObjectsInAnyOrder() {
assertThat(list, containsInAnyOrder(second, first))
}
}
|
Asserções para Mapas (Map)
Se quisermos verificar que um mapa contém uma chave específica, criamos o matcher invocando o método hasKey() da classe Matchers:
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
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasKey
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class MapAssertionTest {
companion object {
private const val KEY = "key"
}
private lateinit var expected: Any
private lateinit var map: MutableMap<String, Any>
@BeforeEach
fun createAndInitializeMap() {
map = mutableMapOf()
expected = Any()
map[KEY] = expected
}
@Test
fun shouldContainCorrectKey() {
assertThat(map, hasKey(KEY))
}
}
|
Se quisermos verificar que um mapa contém um par chave-valor específico (entry), invocamos o método hasEntry() da classe Matchers:
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
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasEntry
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class MapAssertionTest {
companion object {
private const val KEY = "key"
}
private lateinit var expected: Any
private lateinit var map: MutableMap<String, Any>
@BeforeEach
fun createAndInitializeMap() {
map = mutableMapOf()
expected = Any()
map[KEY] = expected
}
@Test
fun shouldContainObjectWithCorrectKey() {
assertThat(map, hasEntry(KEY, expected))
}
}
|
Combinando Múltiplas Asserções do Hamcrest
Quando queremos construir uma asserção composta por múltiplos matchers do Hamcrest, podemos escolher uma destas duas opções:
- Se quisermos garantir que todas as condições sejam satisfeitas simultaneamente (operador lógico
AND), invocamos o método allOf() da classe Matchers.
- Se quisermos garantir que pelo menos uma das condições seja satisfeita (operador lógico
OR), invocamos o método anyOf() da classe Matchers.
Os exemplos a seguir demonstram como combinar múltiplos matchers do Hamcrest ao escrever asserções para um objeto Task. Esses exemplos utilizam o matcher hasProperty(), que recebe dois parâmetros:
- O nome da propriedade (que possui um getter JavaBeans correspondente, como
getId(), getTitle()).
- O matcher do Hamcrest que inspecionará o valor daquela propriedade.
Se quisermos verificar que todos os valores de propriedades do objeto Task criado estão corretos, escrevemos o seguinte método 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
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.hasProperty
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class TaskTest {
companion object {
private const val ID = 1L
private const val ASSIGNEE_ID = 99L
private const val CREATOR_ID = 44L
private const val TITLE = "Write an example project"
private const val DESCRIPTION = "description"
}
private lateinit var task: Task
@BeforeEach
fun createTask() {
task = Task.getBuilder()
.withId(ID)
.withAssignee(ASSIGNEE_ID)
.withCreator(CREATOR_ID)
.withTitle(TITLE)
.withDescription(DESCRIPTION)
.build()
}
@Test
fun build_shouldCreateTaskWithCorrectFieldValues() {
assertThat(
task,
allOf(
hasProperty("id", `is`(ID)),
hasProperty("assignee", hasProperty("userId", `is`(ASSIGNEE_ID))),
hasProperty("closer", nullValue()),
hasProperty("creator", hasProperty("userId", `is`(CREATOR_ID))),
hasProperty("title", `is`(TITLE)),
hasProperty("description", `is`(DESCRIPTION)),
hasProperty("status", `is`(TaskStatus.OPEN)),
hasProperty("resolution", nullValue())
)
)
}
}
|
Por outro lado, se quisermos verificar que pelo menos um dos campos do objeto Task corresponde às condições listadas, utilizamos anyOf():
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
|
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.anyOf
import org.hamcrest.Matchers.hasProperty
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.nullValue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class TaskTest {
companion object {
private const val ID = 1L
private const val ASSIGNEE_ID = 99L
private const val CREATOR_ID = 44L
private const val TITLE = "Write an example project"
private const val DESCRIPTION = "description"
}
private lateinit var task: Task
@BeforeEach
fun createTask() {
task = Task.getBuilder()
.withId(ID)
.withAssignee(ASSIGNEE_ID)
.withCreator(CREATOR_ID)
.withTitle(TITLE)
.withDescription(DESCRIPTION)
.build()
}
@Test
fun build_shouldCreateTaskWithAtLeastOneCorrectFieldValue() {
assertThat(
task,
anyOf(
hasProperty("id", `is`(ID)),
hasProperty("assignee", hasProperty("userId", `is`(ASSIGNEE_ID))),
hasProperty("closer", nullValue()),
hasProperty("creator", hasProperty("userId", `is`(CREATOR_ID))),
hasProperty("title", `is`(TITLE)),
hasProperty("description", `is`(DESCRIPTION)),
hasProperty("status", `is`(TaskStatus.OPEN)),
hasProperty("resolution", nullValue())
)
)
}
}
|
Abaixo apresentamos a classe completa HamcrestAssertionsTest presente em nosso projeto de exemplo, integrando todas as variações de matchers e asserções ensinadas nesta aula:
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
package com.testwithspring.starter.assertions
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anyOf
import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.containsInAnyOrder
import org.hamcrest.Matchers.hasEntry
import org.hamcrest.Matchers.hasKey
import org.hamcrest.Matchers.hasProperty
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import org.hamcrest.Matchers.sameInstance
import org.hamcrest.Matchers.hasSize
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@DisplayName("Asserções expressivas com Hamcrest no JUnit 6")
class HamcrestAssertionsTest {
@Nested
@DisplayName("Asserções para valores booleanos")
inner class BooleanAssertions {
@Test
@DisplayName("Deve verificar que o valor booleano é true")
fun booleanShouldBeTrue() {
val actual = true
assertThat(actual, `is`(true))
assertThat("Expected boolean to be true but was false", actual, `is`(true))
}
@Test
@DisplayName("Deve verificar que o valor booleano é false")
fun booleanShouldBeFalse() {
val actual = false
assertThat(actual, `is`(false))
assertThat("Expected boolean to be false but was true", actual, `is`(false))
}
}
@Nested
@DisplayName("Asserções para objetos e referências")
inner class ObjectAssertions {
@Test
@DisplayName("Deve verificar que o objeto é null")
fun objectShouldBeNull() {
val actual: Any? = null
assertThat(actual, nullValue())
assertThat("Expected object to be null but was: $actual", actual, nullValue())
}
@Test
@DisplayName("Deve verificar que o objeto não é null")
fun objectShouldNotBeNull() {
val actual: Any? = Any()
assertThat(actual, notNullValue())
assertThat("Expected object to be not null but was null", actual, notNullValue())
}
@Test
@DisplayName("Deve verificar igualdade entre inteiros")
fun twoIntegersShouldBeEqual() {
val actual = 9
val expected = 9
assertThat(actual, `is`(expected))
assertThat("Expected integer to be: $expected but was: $actual", actual, `is`(expected))
}
@Test
@DisplayName("Deve verificar desigualdade entre inteiros")
fun twoIntegersShouldNotBeEqual() {
val actual = 9
val expected = 41
assertThat(actual, not(expected))
assertThat("Expected integer to not be: $expected but was: $actual", actual, not(expected))
}
@Test
@DisplayName("Deve verificar que dois objetos apontam para a mesma referência")
fun shouldPointToSameObject() {
val actual = Any()
val expected = actual
assertThat(actual, sameInstance(expected))
assertThat("Expected two objects to be the same but they are not", actual, sameInstance(expected))
}
}
@Nested
@DisplayName("Asserções para coleções e arrays")
inner class CollectionsAndArrayAssertions {
@Test
@DisplayName("Deve verificar que dois arrays são iguais")
fun twoIntArraysShouldBeEqual() {
val actual = intArrayOf(2, 5, 7)
val expected = intArrayOf(2, 5, 7)
assertThat(actual, `is`(expected))
assertThat("Expected array: ${expected.contentToString()} but got: ${actual.contentToString()}", actual, `is`(expected))
}
@Test
@DisplayName("Deve verificar tamanho de lista vazia")
fun sizeOfEmptyListShouldBeZero() {
val emptyList = emptyList<String>()
assertThat(emptyList, hasSize(0))
}
@Test
@DisplayName("Deve verificar elementos de lista na ordem e em qualquer ordem")
fun listShouldContainCorrectElements() {
val first = "first"
val second = "second"
val list = listOf(first, second)
assertThat(list, contains(first, second))
assertThat(list, containsInAnyOrder(second, first))
}
@Test
@DisplayName("Deve verificar chaves e entradas de mapas")
fun mapShouldContainKeyAndEntry() {
val map = mapOf("key" to "value")
assertThat(map, hasKey("key"))
assertThat(map, hasEntry("key", "value"))
}
}
@Nested
@DisplayName("Combinando asserções com allOf e anyOf")
inner class CombiningAssertions {
private val id = 1L
private val assigneeId = 99L
private val creatorId = 44L
private val title = "Write an example project"
private val description = "description"
private lateinit var task: Task
@BeforeEach
fun createTask() {
task = Task.getBuilder()
.withId(id)
.withAssignee(assigneeId)
.withCreator(creatorId)
.withTitle(title)
.withDescription(description)
.build()
}
@Test
@DisplayName("Deve verificar que todas as propriedades da Task estão corretas com allOf")
fun buildShouldCreateTaskWithCorrectFieldValues() {
assertThat(
task,
allOf(
hasProperty("id", `is`(id)),
hasProperty("assignee", hasProperty("userId", `is`(assigneeId))),
hasProperty("closer", nullValue()),
hasProperty("creator", hasProperty("userId", `is`(creatorId))),
hasProperty("title", `is`(title)),
hasProperty("description", `is`(description)),
hasProperty("status", `is`(TaskStatus.OPEN)),
hasProperty("resolution", nullValue())
)
)
}
@Test
@DisplayName("Deve verificar que ao menos uma propriedade está correta com anyOf")
fun buildShouldCreateTaskWithAtLeastOneCorrectFieldValue() {
assertThat(
task,
anyOf(
hasProperty("id", `is`(id)),
hasProperty("assignee", hasProperty("userId", `is`(assigneeId))),
hasProperty("closer", nullValue()),
hasProperty("creator", hasProperty("userId", `is`(creatorId))),
hasProperty("title", `is`(title)),
hasProperty("description", `is`(description)),
hasProperty("status", `is`(TaskStatus.OPEN)),
hasProperty("resolution", nullValue())
)
)
}
}
}
|
Executando os Testes do Hamcrest no Gradle
Ao executarmos a suíte HamcrestAssertionsTest via Gradle (./gradlew test --tests HamcrestAssertionsTest -i), inspecionamos o log de execução detalhado:
Initialized native services in: /home/wc/.gradle/native
Initialized jansi services in: /home/wc/.gradle/native
The client will now receive all logging from the daemon (pid: 16102, address: [fe80::1%1:54330]).
Gradle Daemon started in 1 s 042 ms
Starting Build
Settings evaluated using settings file '/home/wc/test-with-spring/code/01-starter-junit6/settings.gradle.kts'.
Projects loaded. Root project using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Included projects: [root project 'starter-junit6']
> Configure project :
Evaluating root project 'starter-junit6' using build file '/home/wc/test-with-spring/code/01-starter-junit6/build.gradle.kts'.
Using Kotlin Gradle Plugin 2.4.21
Using Java Toolchain: Temurin 21.0.6 (/home/wc/.sdkman/candidates/java/21.0.6-tem)
Spring Boot 4.1.2 plugin applied.
All projects evaluated.
Selected primary task 'test' from project :
Tasks to be executed: [task ':compileKotlin', task ':compileJava', task ':processResources', task ':classes', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were skipped: [task ':compileJava', task ':compileTestJava']
> Task :compileKotlin UP-TO-DATE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin
Compiling with Kotlin compiler version 2.4.21 (JVM target 21)
Compiling Kotlin files from [/home/wc/test-with-spring/code/01-starter-junit6/src/test/kotlin]
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
Starting process 'Gradle Test Executor 1'. Working directory: /home/wc/test-with-spring/code/01-starter-junit6 Command: /home/wc/.sdkman/candidates/java/21.0.6-tem/bin/java -ea -cp ...
Successfully started process 'Gradle Test Executor 1'
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para valores booleanos > Deve verificar que o valor booleano é true PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para valores booleanos > Deve verificar que o valor booleano é false PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para objetos e referências > Deve verificar que o objeto é null PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para objetos e referências > Deve verificar que o objeto não é null PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para objetos e referências > Deve verificar igualdade entre inteiros PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para objetos e referências > Deve verificar desigualdade entre inteiros PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para objetos e referências > Deve verificar que dois objetos apontam para a mesma referência PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para coleções e arrays > Deve verificar que dois arrays são iguais PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para coleções e arrays > Deve verificar tamanho de lista vazia PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para coleções e arrays > Deve verificar elementos de lista na ordem e em qualquer ordem PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Asserções para coleções e arrays > Deve verificar chaves e entradas de mapas PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Combinando asserções com allOf e anyOf > Deve verificar que todas as propriedades da Task estão corretas com allOf PASSED
com.testwithspring.starter.assertions.HamcrestAssertionsTest > Combinando asserções com allOf e anyOf > Deve verificar que ao menos uma propriedade está correta com anyOf PASSED
HamcrestAssertionsTest > 13 tests completed, 0 failed, 0 skipped
Finished generating test XML results (0.014 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/test-results/test
Generating HTML test report...
Finished generating test html results (0.022 secs) into: /home/wc/test-with-spring/code/01-starter-junit6/build/reports/tests/test
BUILD SUCCESSFUL in 1s
5 actionable tasks: 2 executed, 3 up-to-date
Quando Devemos Usar o Hamcrest?
Os exemplos acima demonstram que escrever asserções com o Hamcrest é uma escolha muito superior às asserções padrão do JUnit (assertEquals) por três razões:
- Passamos os valores na ordem natural de raciocínio: primeiro o valor obtido (
actual), depois o matcher esperado (expected).
- Os matchers do Hamcrest fazem com que as asserções sejam lidas quase como uma frase em inglês natural (
assertThat(actual, is(expected))), facilitando a leitura do teste.
- O método
assertThat(T actual, Matcher<? super T> matcher) protege o desenvolvedor contra inversões acidentais: como o segundo parâmetro exige um tipo Matcher, é impossível inverter actual e expected sem que o compilador aponte erro imediatamente!
Em testes unitários puros de domínio em Kotlin, costumamos preferir o AssertJ (que veremos a partir da aula 04 deste tópico) ou Kotest Matchers porque o autocompletar fluente (.) na IDE é ainda mais produtivo do que importar dezenas de métodos estáticos da classe Matchers.
No entanto, conhecer bem o Hamcrest é indispensável no ecossistema Spring: quando escrevemos testes para controladores web e APIs REST utilizando o MockMvc (Spring MVC Test framework), os métodos de verificação de JSON (jsonPath("$.title", is("..."))), modelo (model().attribute(...)) e conteúdo utilizam matchers do Hamcrest nativamente!
Vamos resumir o que aprendemos nesta aula.
Resumo
Esta aula nos ensinou quatro pontos principais:
- Para escrever asserções com Hamcrest no JUnit 6, utilizamos o método
MatcherAssert.assertThat().
- Para combinar múltiplos matchers do Hamcrest em uma única verificação, usamos os métodos
allOf() (E lógico) ou anyOf() (OU lógico) da classe Matchers.
- Escrever asserções com Hamcrest é muito mais seguro e legível do que usar
assertEquals(expected, actual) da API básica do JUnit.
- Os matchers do Hamcrest são amplamente utilizados em conjunto com o
MockMvc ao testar endpoints web e JSON no Spring MVC Test Framework.