Escrevendo Asserções Expressivas com kotest-assertions-core
Depois de concluirmos esta aula, nós:
- Saberemos como adicionar asserções expressivas aos blocos das nossas especificações usando
kotest-assertions-core. - Saberemos como escrever asserções para as exceções lançadas pelo sistema sob especificação (
shouldThrow,shouldNotThrow). - Compreenderemos como utilizar matchers especializados de coleções e objetos (
shouldHaveSize,shouldContain,shouldContainExactly) e agrupar múltiplas asserções comassertSoftly.
1. Introdução às Asserções no Kotest
No Spock Framework, escrevíamos condições booleanas implícitas dentro dos blocos then: e expect:. No Kotest 6, utilizamos a biblioteca kotest-assertions-core, que aproveita as infix functions e extension functions do Kotlin para criar asserções fluentes que se leem como frases em inglês natural (actual shouldBe expected).
Para escrever especificações fáceis de ler e dar manutenção, seguimos duas regras de ouro:
- Coloque as asserções mais importantes no início do bloco
Then(oushould): se o teste falhar logo na pré-condição principal (por exemplo, o tamanho da lista retornada ou o status da resposta), você diagnostica o problema imediatamente. - Separe grupos lógicos de asserções em blocos
Anddescritivos: se suas asserções verificam aspectos distintos do resultado, divida o blocoThenusandoAnd("...")com descrições claras.
Vamos ver como escrever asserções básicas nos blocos Then e should.
2. Escrevendo Asserções nos Blocos Then e should
Vamos analisar dois exemplos práticos que demonstram como adicionar asserções às nossas especificações na classe SimpleConditionSpec:
Primeiro, vamos verificar que a criação de uma nova lista resulta em uma lista vazia com tamanho zero dentro de um bloco When / Then:
|
|
[!NOTE] Na prática, não precisamos de ambas as linhas acima (
list.shouldBeEmpty()já verifica se a lista está vazia e produz uma mensagem de erro riquíssima caso contenha elementos). Incluímos ambas aqui apenas para demonstrar que podemos combinar matchers de extensão específicos (shouldBeEmpty()) com o matcher de igualdade geral (shouldBe).
Segundo, vamos verificar que o método Math.max() retorna o maior entre dois números inteiros usando um bloco de expectativa direta (should):
|
|
A seguir, vamos descobrir como escrever asserções para exceções lançadas pelo sistema sob especificação.
3. Escrevendo Asserções para Exceções (shouldThrow e shouldNotThrow)
Quando queremos verificar se uma ação lança (ou não lança) uma exceção no Kotest 6, utilizamos as funções reificadas shouldThrow<T> { ... } e shouldNotThrow<T> { ... } (ou shouldNotThrowAny { ... }). Vamos examinar três casos de uso fundamentais!
3.1. Verificando que uma Exceção Específica É Lançada
Se quisermos verificar que tentar remover um elemento de uma pilha (Stack) vazia lança EmptyStackException, envolvemos a chamada stack.pop() com shouldThrow<EmptyStackException>:
|
|
3.2. Verificando que uma Exceção NÃO É Lançada
Se quisermos garantir explicitamente que remover um elemento de uma pilha que contém um item não lança EmptyStackException, utilizamos shouldNotThrow<EmptyStackException>:
|
|
3.3. Inspecionando a Exceção Lançada
Muitas vezes, não basta saber que uma RuntimeException foi lançada: precisamos inspecionar sua mensagem (message), código de erro customizado ou causa raiz (cause).
No Kotest, a função shouldThrow<T> { ... } retorna a instância tipada da exceção capturada! Assim, podemos armazená-la em uma variável val ex e fazer quantas asserções adicionais quisermos sobre ela:
|
|
4. Usando Matchers Especializados de Coleções e assertSoftly
No Spock Framework, recorríamos a matchers externos do Hamcrest (hasSize(1), contains('Hello World!')) quando queríamos asserções expressivas para coleções.
No Kotest 6, o próprio kotest-assertions-core já inclui centenas de matchers nativos em Kotlin para coleções, strings, mapas, datas, números e JSON, usando a sintaxe infixa:
[objeto ou coleção] [matcher] [valor esperado]
Vamos verificar que nosso método adiciona um item em uma lista em dois passos claros separados por And:
- Verificar que a lista possui exatamente 1 item (
list shouldHaveSize 1). - Garantir que a lista contém o item esperado (
list shouldContain "Hello World!"oulist.shouldContainExactly("Hello World!")).
|
|
[!TIP] Dica Pro: Agrupando asserções com
assertSoftlySe você estiver validando várias propriedades de um mesmo DTO ou entidade dentro de um bloco
Then, envolva as asserções comassertSoftly(returned) { ... }. Dessa forma, se a primeira propriedade falhar, o Kotest continuará avaliando todas as demais e exibirá um relatório consolidado com todas as divergências de uma só vez!
Resumo
Nesta aula, aprendemos cinco pontos importantes:
- Especificamos o estado esperado utilizando os matchers fluentes do
kotest-assertions-core(shouldBe,shouldBeEmpty, etc.) dentro dos blocosThenoushould. - Devemos posicionar as asserções mais importantes no início do bloco
Then. - Quando nossas asserções pertencem a grupos lógicos distintos, dividimos a verificação usando blocos
And("...")com boas descrições. - Capturamos e validamos exceções de forma tipada usando
val ex = shouldThrow<MinhaException> { ... }. - O Kotest oferece matchers nativos riquíssimos para coleções (
shouldHaveSize,shouldContainExactly) sem depender do Hamcrest.