Escrevendo Asserções Fluentes com AssertJ 3.27
Ao concluirmos esta aula, nós:
- Seremos capazes de escrever asserções fluentes e encadeadas com o AssertJ 3.27 em Kotlin.
- Compreenderemos por que devemos utilizar o AssertJ como nossa biblioteca padrão de asserções em testes JUnit Jupiter.
Configurando as Dependências Necessárias
Antes de podermos escrever asserções com o AssertJ, precisamos garantir que a dependência assertj-core esteja declarada em nosso script de build. Na stack moderna com Java 21+ / Kotlin 2.4 e Spring Boot 4.1, utilizamos a série 3.27.x (que já vem incluída automaticamente quando usamos spring-boot-starter-test).
Se estivermos usando Maven em um projeto isolado, declaramos a dependência no arquivo pom.xml:
|
|
Se estivermos usando Gradle 9 (Kotlin DSL), declaramos a dependência no arquivo build.gradle.kts:
|
|
Depois de adicionarmos essa dependência ao nosso script de build, podemos começar a escrever asserções com o AssertJ.
Escrevendo Asserções com AssertJ
Quando queremos escrever asserções com o AssertJ, utilizamos o método estático assertThat() da classe org.assertj.core.api.Assertions. Esse método recebe o valor ou objeto real obtido (actual) como seu único parâmetro e retorna um objeto de asserção fortemente tipado específico para o tipo de actual. A partir do retorno de assertThat(actual), basta digitar . na IDE para descobrir todos os métodos de verificação disponíveis para aquele tipo!
Como o AssertJ fornece uma API fluente, cada método de asserção retorna uma referência para o próprio “objeto de asserção”. Isso significa que podemos encadear múltiplas asserções sobre o mesmo alvo de forma contínua e legível.
Por exemplo, se quisermos escrever uma asserção que verifica simultaneamente se o tamanho de uma lista está correto e se a lista contém os objetos esperados na ordem exata, escrevemos:
|
|
Se quisermos fornecer uma mensagem de erro customizada que será exibida quando nossa asserção falhar, podemos escolher uma destas duas opções da classe base AbstractAssert:
- Se quisermos sobrescrever apenas o prefixo/descrição da mensagem de erro (mantendo os detalhes do
ExpectedeActualgerados pelo AssertJ), invocamos o métododescribedAs()(ou`as`()entre crases em Kotlin, poisasé palavra reservada de cast no Kotlin) antes do método de verificação. - Se quisermos sobrescrever a mensagem de erro inteira, invocamos o método
overridingErrorMessage()(ouwithFailMessage()) antes do método de verificação.
O código-fonte a seguir demonstra o uso de ambas as opções:
|
|
Veja a diferença prática na saída quando cada uma dessas asserções falha:
Se a asserção que invoca describedAs() falhar, veremos a descrição entre colchetes seguida pela comparação padrão:
|
|
Já se a asserção que invoca overridingErrorMessage() falhar, veremos exatamente a mensagem customizada que definimos:
|
|
overridingErrorMessage() porque ela substitui toda a mensagem de erro. No entanto, se você preferir apenas adicionar contexto mantendo o comparativo padrão do AssertJ, sinta-se à vontade para usar describedAs()!
Vamos avançar e examinar exemplos que demonstram como escrever asserções simples com o AssertJ para os tipos mais comuns.
Asserções para Valores Booleanos
Se quisermos verificar que um valor Boolean é true, invocamos o método isTrue() da classe AbstractBooleanAssert:
|
|
Se quisermos verificar que um valor Boolean é false, invocamos o método isFalse() da classe AbstractBooleanAssert:
|
|
Asserções para Verificar se um Objeto É null ou Não É null
Se quisermos verificar que um objeto é null, invocamos o método isNull() da classe AbstractAssert:
|
|
Se quisermos verificar que um objeto não é null, invocamos o método isNotNull() da classe AbstractAssert:
|
|
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, podemos invocar tanto o método isEqualTo() da classe AbstractAssert quanto o método isEqualByComparingTo() da classe AbstractComparableAssert. A diferença entre eles é crucial:
- O método
isEqualTo()verifica igualdade invocando o métodoequals(). - O método
isEqualByComparingTo()verifica equivalência invocando o métodocompareTo()da interfaceComparable(muito útil paraBigDecimal, ondenew BigDecimal("10.0")enew BigDecimal("10.00")têmequals()falso devido à escala, mascompareTo() == 0verdadeiro!).
Para verificar que dois números inteiros (Int) são iguais:
|
|
Se quisermos verificar que o valor real obtido não é igual ao valor esperado, invocamos isNotEqualTo() ou isNotEqualByComparingTo():
|
|
Asserções de Referência de Objetos
Se quisermos garantir que duas variáveis apontam exatamente para a mesma instância de objeto na memória (===), invocamos o método isSameAs() da classe AbstractAssert:
|
|
Se quisermos garantir que dois objetos não apontam para a mesma referência na memória (!==), invocamos o método isNotSameAs() da classe AbstractAssert:
|
|
Asserções de Igualdade entre Dois Arrays
Se quisermos verificar que dois arrays possuem exatamente os mesmos elementos na mesma ordem, invocamos o método isEqualTo() da classe AbstractArrayAssert:
|
|
Se quisermos verificar que dois arrays não são iguais, invocamos o método isNotEqualTo() da classe AbstractArrayAssert:
|
|
Asserções para Listas (List)
Se quisermos verificar o tamanho de uma lista com o AssertJ, utilizamos uma destas duas opções da classe AbstractIterableAssert:
- Para verificar que uma lista está vazia, invocamos o método
isEmpty(). - Para verificar que uma lista possui um tamanho específico, invocamos o método
hasSize().
Veja o exemplo abaixo que valida ambas as situações:
|
|
Se quisermos garantir que uma lista contém ou não contém objetos específicos, o AssertJ oferece métodos claros e diretos:
- Para verificar que a lista contém apenas os objetos especificados na ordem exata, usamos
containsExactly(). - Para verificar que a lista contém apenas os objetos especificados em qualquer ordem, usamos
containsExactlyInAnyOrder(). - Para garantir que a lista não contém um objeto indesejado, invocamos
doesNotContain().
Veja a implementação completa:
|
|
Asserções para Mapas (Map)
Se quisermos verificar se um mapa contém uma chave esperada ou se não contém uma chave incorreta, utilizamos os métodos da classe AbstractMapAssert:
- Para verificar que o mapa contém a chave esperada, invocamos
containsKey(). - Para verificar que o mapa não contém uma chave incorreta, invocamos
doesNotContainKey().
|
|
Se quisermos verificar se um mapa contém uma entrada (entry chave-valor) específica ou se não contém uma entrada incorreta:
- Para garantir que o mapa contém a entrada correta, invocamos
containsEntry(key, value). - Para verificar que o mapa não contém uma entrada específica, invocamos
doesNotContainEntry(key, value).
|
|
Agora que dominamos as asserções fundamentais com o AssertJ, vamos entender por que devemos adotá-lo em vez das asserções padrão do JUnit ou do Hamcrest.
Por Que Devemos Usar o AssertJ?
Existem três razões decisivas para escrevermos nossas asserções com o AssertJ:
- Asserções com AssertJ são muito mais rápidas de escrever do que com JUnit ou Hamcrest: Você só precisa importar um único método (
Assertions.assertThat), passar o objeto realassertThat(actual)e a IDE lista automaticamente apenas os métodos compatíveis com aquele tipo (List,Map,String,Int), sem precisar lembrar nomes de matchers estáticos do Hamcrest. - O AssertJ protege seus usuários contra erros de programação: Como
assertThat(actual)recebe apenas o valor obtido e retorna o objeto de asserção onde encadeamos.isEqualTo(expected), é praticamente impossível inverter a ordem entreactualeexpected. - O AssertJ nos ajuda a escrever asserções naturais e encadeáveis: Ele segue perfeitamente a estrutura mental “Quando invocamos o método X, o valor Y é retornado” e possui métodos negativos nativos para todos os cenários (
doesNotContain,doesNotContainKey,isNotSameAs), algo que faltava no Hamcrest básico.
Vamos comparar lado a lado as três abordagens para verificar a igualdade de dois inteiros:
|
|
Tanto o Hamcrest quanto o AssertJ são muito mais fáceis de ler que o assertEquals(expected, actual) tradicional, mas a API fluente e fortemente tipada do AssertJ torna a experiência de escrita na IDE imbatível.
Resumo
Esta aula nos ensinou três pontos fundamentais:
- No ecossistema moderno com Java 21+ e Kotlin 2.4, utilizamos o
org.assertj:assertj-core:3.27.7. - Escrevemos asserções fluentes com o AssertJ invocando o ponto de entrada único
org.assertj.core.api.Assertions.assertThat(actual). - Devemos priorizar o AssertJ em nossos testes porque sua API fluente previne erros de inversão de argumentos, suporta autocompletar contextual na IDE e gera código fácil de ler e manter.