DEV Community

Diego de Sousa Brandão
Diego de Sousa Brandão

Posted on

Maratona de Testes Automatizados — Step 1: Testes Unitários

Maratona de Testes em Java: Da Teoria à Prática

Step 1: Testes Unitários - A Base Sólida da Pirâmide de Testes

Índice

  1. Por que testes unitários são fundamentais?
  2. O que realmente define um teste unitário?
  3. O que exatamente devemos testar?
  4. Estruturando seus testes com clareza
  5. As ferramentas do ofício: JUnit e Mockito
  6. Exemplo completo: Testando um Controller REST
  7. Solitary vs. Sociable: Duas abordagens
  8. DRY vs. DAMP nos testes
  9. Evitando armadilhas comuns
  10. Práticas avançadas e técnicas eficazes
  11. Integração Contínua
  12. Lições finais
  13. Conclusão
  14. Recursos recomendados
  15. Referências Bibliográficas

Por que testes unitários são fundamentais?

Testes unitários são como os alicerces de um edifício: invisíveis para o usuário final, mas absolutamente essenciais para a integridade e sustentação de todo o sistema. Eles são a base da "Pirâmide de Testes", um conceito desenvolvido por Mike Cohn que organiza os testes em camadas de diferentes granularidades, indicando também quantos testes devemos ter em cada uma dessas camadas.

No artigo "The Practical Test Pyramid", Ham Vocke destaca que "seus testes unitários serão muito rápidos. Em uma máquina razoável, você poderá rodar milhares deles em poucos minutos." Esta velocidade é crucial para o desenvolvimento ágil, permitindo que você obtenha feedback praticamente instantâneo sobre suas alterações de código.

O que realmente define um teste unitário?

Um teste unitário eficaz possui estas características essenciais:

Característica Descrição Por quê isso importa
⚡ Rápido Executa em milissegundos Permite feedback imediato durante o desenvolvimento
🧩 Isolado Sem dependência externa (rede, banco, disco) Evita falhas por causas externas à unidade testada
🔄 Determinístico Sempre produz o mesmo resultado Elimina testes "flaky" (instáveis)
🎯 Focado Verifica uma única lógica ou comportamento Facilita identificar exatamente o que falhou
📖 Legível Clara separação entre cenário e verificação Permite manutenção e entendimento a longo prazo

Como Ham Vocke menciona, "teste pequenas partes da sua base de código em isolamento e evite acessar bancos de dados, o sistema de arquivos ou fazer requisições HTTP." Este isolamento é o que torna os testes unitários verdadeiramente "unitários".

O que exatamente devemos testar?

Segundo Vocke, "você pode escrever testes unitários para todas as classes do seu código de produção, independentemente da funcionalidade delas." Isso significa que quase tudo pode e deve ser testado unitariamente:

  • Classes de domínio com regras de negócio
  • Controllers (testar mapeamentos e respostas)
  • Services (testar fluxos e orquestrações)
  • Repositórios com lógica própria
  • Utils, validators, formatters (funções de propósito específico)

O importante é focar no comportamento observável da unidade, não em sua implementação interna. Como Vocke recomenda, devemos "testar para o comportamento observável em vez de refletir a estrutura interna do código em nossos testes."

Estruturando seus testes com clareza

Ham Vocke destaca duas formas eficazes de estruturar testes, que ele chama de "uma boa estrutura para todos os seus testes":

📐 1. Arrange → Act → Assert

@Test
public void deveCalcularValorTotalComDesconto() {
    // Arrange - Prepara o cenário
    Produto produto = new Produto("Notebook", 2000.0);
    Cliente clienteVip = new Cliente("João", TipoCliente.VIP);

    // Act - Executa a ação
    double valorFinal = calculadoraPreco.calcular(produto, clienteVip);

    // Assert - Verifica o resultado
    assertEquals(1800.0, valorFinal, 0.001);
}
Enter fullscreen mode Exit fullscreen mode

💬 2. Given → When → Then (estilo BDD)

@Test
public void clienteVipDeveReceberDescontoEspecial() {
    // Given - Dado um cliente VIP e um produto
    Cliente clienteVip = new Cliente("Maria", TipoCliente.VIP);
    Produto produto = new Produto("Smartphone", 1000.0);

    // When - Quando calcular o preço final
    double valorFinal = calculadoraPreco.calcular(produto, clienteVip);

    // Then - Então deve aplicar 10% de desconto
    assertEquals(900.0, valorFinal, 0.001);
}
Enter fullscreen mode Exit fullscreen mode

Vocke sugere que este padrão "pode ser aplicado a outros testes de nível mais alto também. Em todos os casos, eles garantem que seus testes permaneçam fáceis e consistentes de ler."

As ferramentas do ofício: JUnit e Mockito

JUnit 5

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;

class CalculadoraTest {

    private Calculadora calc;

    @BeforeEach
    void setup() {
        calc = new Calculadora();
    }

    @Test
    @DisplayName("Soma de dois números positivos")
    void deveSomarDoisNumerosPositivos() {
        // Act
        int resultado = calc.somar(5, 3);

        // Assert
        assertEquals(8, resultado);
    }

    @Test
    @DisplayName("Divisão por zero deve lançar exceção")
    void deveLancarExcecaoAoDividirPorZero() {
        // Assert + Act (verificando exceção)
        assertThrows(ArithmeticException.class, () -> {
            calc.dividir(10, 0);
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Mockito

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class PedidoServiceTest {

    @Mock
    DescontoService descontoService;

    @Mock
    EmailService emailService;

    @InjectMocks
    PedidoService pedidoService;

    @Test
    void deveAplicarDescontoAoFinalizarPedido() {
        // Arrange
        Pedido pedido = new Pedido(1L, 100.0);
        when(descontoService.calcularDesconto(pedido)).thenReturn(10.0);

        // Act
        pedidoService.finalizarPedido(pedido);

        // Assert
        assertEquals(90.0, pedido.getValorTotal());
        verify(emailService).enviarConfirmacao(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo completo: Testando um Controller REST

Vamos ver um exemplo prático de como testar um controller REST em Spring Boot:

O Controller em produção

@RestController
@RequestMapping("/api/produtos")
public class ProdutoController {

    private final ProdutoService produtoService;

    public ProdutoController(ProdutoService produtoService) {
        this.produtoService = produtoService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProdutoDTO> buscarPorId(@PathVariable Long id) {
        return produtoService.buscarPorId(id)
                .map(produto -> ResponseEntity.ok(new ProdutoDTO(produto)))
                .orElse(ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<ProdutoDTO> criar(@RequestBody @Valid ProdutoRequest request) {
        Produto produto = produtoService.criar(
            new Produto(request.getNome(), request.getPreco())
        );
        return ResponseEntity
            .created(URI.create("/api/produtos/" + produto.getId()))
            .body(new ProdutoDTO(produto));
    }
}
Enter fullscreen mode Exit fullscreen mode

Teste unitário com MockMVC

Ham Vocke explica que ferramentas como MockMVC são importantes porque "Spring MVC's controller faz uso intensivo de anotações para declarar em quais caminhos eles estão escutando, quais verbos HTTP usar, quais parâmetros eles analisam do caminho da URL ou parâmetros de consulta e assim por diante. Simplesmente invocar um método do controller em seus testes unitários não testará todas essas coisas cruciais."

@ExtendWith(MockitoExtension.class)
class ProdutoControllerTest {

    private MockMvc mockMvc;

    @Mock
    private ProdutoService produtoService;

    @InjectMocks
    private ProdutoController produtoController;

    @BeforeEach
    void setup() {
        mockMvc = MockMvcBuilders
            .standaloneSetup(produtoController)
            .build();
    }

    @Test
    void deveRetornarProdutoQuandoExistir() throws Exception {
        // Arrange
        Produto produto = new Produto("Teclado Mecânico", 350.0);
        produto.setId(1L);

        when(produtoService.buscarPorId(1L))
            .thenReturn(Optional.of(produto));

        // Act & Assert
        mockMvc.perform(get("/api/produtos/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.nome").value("Teclado Mecânico"))
            .andExpect(jsonPath("$.preco").value(350.0));
    }

    @Test
    void deveRetornarNotFoundQuandoProdutoNaoExistir() throws Exception {
        // Arrange
        when(produtoService.buscarPorId(99L))
            .thenReturn(Optional.empty());

        // Act & Assert
        mockMvc.perform(get("/api/produtos/99"))
            .andExpect(status().isNotFound());
    }

    @Test
    void deveCriarNovoProduto() throws Exception {
        // Arrange
        Produto produtoParaSalvar = new Produto("Mouse", 89.9);
        Produto produtoSalvo = new Produto("Mouse", 89.9);
        produtoSalvo.setId(42L);

        when(produtoService.criar(any(Produto.class)))
            .thenReturn(produtoSalvo);

        // Act & Assert
        mockMvc.perform(post("/api/produtos")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"nome\":\"Mouse\",\"preco\":89.9}")
        )
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/api/produtos/42"))
            .andExpect(jsonPath("$.nome").value("Mouse"))
            .andExpect(jsonPath("$.preco").value(89.9));
    }
}
Enter fullscreen mode Exit fullscreen mode

Solitary vs. Sociable: Duas abordagens para testes unitários

Segundo Vocke, "alguns argumentam que todos os colaboradores da sua unidade sob teste devem ser substituídos por mocks ou stubs", enquanto "outros argumentam que apenas colaboradores que são lentos ou têm efeitos colaterais maiores deveriam ser substituídos por stubs ou mocks."

Este conceito foi criado por Jay Fields em seu livro "Working Effectively with Unit Tests", que cunhou os termos "testes unitários solitários" para testes que substituem todos os colaboradores, e "testes unitários sociáveis" para testes que permitem interações com colaboradores reais.

Ham Vocke confessa: "Eu me vejo usando ambas as abordagens o tempo todo. Se se torna estranho usar colaboradores reais, usarei mocks e stubs generosamente. Se eu sentir que envolver o colaborador real me dá mais confiança em um teste, apenas faço stub das partes mais externas do meu serviço."

Exemplo de teste solitário (com mocks)

@Test
void deveCalcularImpostoSobreVendaComMocks() {
    // Arrange
    BigDecimal valorBase = new BigDecimal("1000.00");

    // Mockando todas as dependências
    when(aliquotaService.buscarAliquota("SP"))
        .thenReturn(new BigDecimal("0.18"));

    when(descontoService.calcularDesconto(valorBase))
        .thenReturn(new BigDecimal("100.00"));

    // Act
    BigDecimal imposto = calculadoraImpostoService.calcular(valorBase, "SP");

    // Assert
    assertEquals(new BigDecimal("162.00"), imposto); // (1000 - 100) * 0.18
}
Enter fullscreen mode Exit fullscreen mode

Exemplo de teste sociável (com colaboradores reais)

@Test
void deveCalcularImpostoSobreVendaComColaboradoresReais() {
    // Arrange
    BigDecimal valorBase = new BigDecimal("1000.00");

    // Usando implementação real do serviço de desconto
    DescontoService descontoServiceReal = new DescontoServiceImpl();

    // Ainda mockando o serviço de alíquota (externo)
    AliquotaService aliquotaMock = mock(AliquotaService.class);
    when(aliquotaMock.buscarAliquota("SP"))
        .thenReturn(new BigDecimal("0.18"));

    // Injetando uma mistura de dependências reais e mockadas
    CalculadoraImpostoService calculadora = 
        new CalculadoraImpostoServiceImpl(descontoServiceReal, aliquotaMock);

    // Act
    BigDecimal imposto = calculadora.calcular(valorBase, "SP");

    // Assert
    assertEquals(new BigDecimal("180.00"), imposto); // Considerando que o desconto real é 0
}
Enter fullscreen mode Exit fullscreen mode

DRY vs. DAMP nos testes: Equilibrando clareza e repetição

Ham Vocke alerta: "Não tente ser excessivamente DRY (Don't Repeat Yourself). Duplicação é aceitável se melhorar a legibilidade."

Em testes, muitas vezes é melhor priorizar a clareza em vez da não-repetição. Um acrônimo útil para recordar é DAMP: Descriptive And Meaningful Phrases.

Exemplo questionável (muito DRY):

// Definição central, reusada em todos os testes
private Cliente clienteVip;
private Produto produtoPadrao;

@BeforeEach
void setup() {
    clienteVip = new Cliente("João", TipoCliente.VIP);
    produtoPadrao = new Produto("Item", 100.0);
}

@Test
void deveAplicarDescontoVipEmProduto() {
    assertEquals(90.0, calculadoraPreco.calcular(produtoPadrao, clienteVip));
}

@Test
void deveIncluirFreteGratisPara() {
    assertTrue(beneficioService.temFreteGratis(clienteVip, produtoPadrao));
}
Enter fullscreen mode Exit fullscreen mode

Exemplo melhor (mais DAMP):

@Test
void deveAplicarDescontoVipEmProduto() {
    // Arrange - Dados específicos para este teste
    Cliente clienteVip = new Cliente("João", TipoCliente.VIP);
    Produto notebook = new Produto("Notebook", 2000.0);

    // Act
    double valorComDesconto = calculadoraPreco.calcular(notebook, clienteVip);

    // Assert - Expectativa clara
    assertEquals(1800.0, valorComDesconto, 
                "Cliente VIP deve receber 10% de desconto");
}

@Test
void deveIncluirFreteGratisPara() {
    // Arrange - Dados específicos para este teste
    Cliente clienteVip = new Cliente("Maria", TipoCliente.VIP);
    Produto smartTV = new Produto("Smart TV", 1500.0);

    // Act
    boolean temFreteGratis = beneficioService.temFreteGratis(clienteVip, smartTV);

    // Assert - Expectativa clara
    assertTrue(temFreteGratis, 
              "Cliente VIP deve ter frete grátis independente do produto");
}
Enter fullscreen mode Exit fullscreen mode

Evitando armadilhas comuns

"Mas eu preciso testar este método privado!"

Ham Vocke alerta: "Se você já se encontrou em uma situação em que realmente precisa testar um método privado, você deve dar um passo atrás e se perguntar por quê." Ele continua: "Estou bastante certo de que isso é mais um problema de design do que um problema de escopo."

Quando você sente essa necessidade, geralmente há um problema de design da classe — ela provavelmente está fazendo muitas coisas e violando o princípio da responsabilidade única.

A solução mais elegante:

// Antes: Classe grande com método privado complexo
public class FaturamentoService {
    public BigDecimal calcular(Pedido pedido) {
        // lógica...
        return calcularImpostos(valorBase); // método privado complexo
    }

    private BigDecimal calcularImpostos(BigDecimal valor) {
        // lógica complexa de cálculo de impostos
    }
}

// Depois: Extrair para uma classe dedicada com interface pública
public class FaturamentoService {
    private final ImpostoCalculator impostoCalculator;

    public FaturamentoService(ImpostoCalculator impostoCalculator) {
        this.impostoCalculator = impostoCalculator;
    }

    public BigDecimal calcular(Pedido pedido) {
        // lógica...
        return impostoCalculator.calcular(valorBase); // Agora é público!
    }
}

public class ImpostoCalculator {
    public BigDecimal calcular(BigDecimal valor) {
        // mesma lógica complexa, agora testável
    }
}
Enter fullscreen mode Exit fullscreen mode

"Preciso ter 100% de cobertura!"

Vocke argumenta contra testar código trivial: "Sim, você deve testar a interface pública. Mais importante, no entanto, você não testa código trivial. Não se preocupe, Kent Beck disse que tudo bem. Você não ganhará nada testando getters ou setters simples ou outras implementações triviais."

Focar em cobertura por si só pode levar a testes ruins que não adicionam valor. Busque qualidade em vez de quantidade.

Práticas avançadas e técnicas eficazes

À medida que você ganha experiência em testes unitários, algumas técnicas avançadas podem ajudar a tornar seus testes mais elegantes, legíveis e fáceis de manter. Vejamos três técnicas importantes que podem elevar significativamente a qualidade da sua suite de testes.

1. Dados de teste expressivos

O que é: Métodos de fábrica (factory methods) ou objetos builders que criam dados de teste com nomes semânticos, em vez de usar configurações inline de objetos diretamente nos testes.

Quando usar:

  • Quando os objetos de teste têm muitas propriedades que precisam ser configuradas
  • Quando você repete a mesma configuração de objetos em vários testes
  • Quando a configuração obscurece a intenção principal do teste

Benefícios:

  • Foco no "o quê" está sendo testado, não no "como" os objetos são criados
  • Leitura mais natural - o nome do método descreve o cenário
  • Encapsulamento de detalhes irrelevantes para a intenção do teste
  • Fácil reutilização entre testes similares
// Ruim: Dados aleatórios desconexos
@Test
void deveAprovarCreditoParaClienteBomPagador() {
    Cliente cliente = new Cliente("X123", 35);
    cliente.setScoreCredito(750);
    cliente.setRendaMensal(5000.0);

    boolean aprovado = creditoService.analisarCredito(cliente, 10000.0);

    assertTrue(aprovado);
}

// Melhor: Nome do método de fábrica expressivo
@Test
void deveAprovarCreditoParaClienteBomPagador() {
    Cliente clienteBomPagador = criarClienteComBomHistoricoCredito();

    boolean aprovado = creditoService.analisarCredito(clienteBomPagador, 10000.0);

    assertTrue(aprovado);
}

private Cliente criarClienteComBomHistoricoCredito() {
    Cliente cliente = new Cliente("Maria", 35);
    cliente.setScoreCredito(750);
    cliente.setRendaMensal(5000.0);
    return cliente;
}
Enter fullscreen mode Exit fullscreen mode

Você também pode evoluir para padrões mais sofisticados como builders fluentes:

// Usando um builder expressivo
@Test
void deveAprovarCreditoParaClienteBomPagador() {
    Cliente clienteBomPagador = ClienteBuilder.padrao()
        .comBomHistoricoCredito()
        .comRendaAdequada()
        .build();

    boolean aprovado = creditoService.analisarCredito(clienteBomPagador, 10000.0);

    assertTrue(aprovado);
}
Enter fullscreen mode Exit fullscreen mode

2. Assertion libraries expressivas

O que são: Bibliotecas que oferecem uma sintaxe mais fluente e legível para verificações, substituindo os métodos padrão do JUnit.

Quando usar:

  • Para verificações complexas ou múltiplas em um único objeto
  • Quando se deseja uma leitura mais próxima da linguagem natural
  • Para verificações de coleções, onde se quer verificar múltiplos aspectos

Bibliotecas populares:

  • AssertJ: fluente e orientada a objetos
  • Hamcrest: baseada em matchers
  • Truth (Google): alternativa simples e expressiva

Benefícios:

  • Testes mais legíveis, quase como frases em inglês
  • Mensagens de erro mais descritivas quando falham
  • Encadeamento de verificações reduz duplicação
  • Maior expressividade para casos complexos
// JUnit padrão - Funcionais mas menos expressivas
assertEquals(3, lista.size());
assertTrue(lista.contains("item1"));
assertTrue(cliente.isAtivo());

// AssertJ - Mais fluente e expressivo
assertThat(lista)
    .hasSize(3)
    .contains("item1")
    .doesNotContain("item99");

assertThat(cliente.isAtivo()).isTrue();
Enter fullscreen mode Exit fullscreen mode

Quando um teste falha, a mensagem de erro com AssertJ é mais informativa:

Expected size:<3> but was:<2>
Expected to contain:<"item1"> but did not
Enter fullscreen mode Exit fullscreen mode

3. Testes parametrizados para casos similares

O que são: Testes que executam o mesmo código com diferentes entradas e expectativas de saída, gerando automaticamente múltiplos casos de teste a partir de uma única definição.

Quando usar:

  • Para testar múltiplas variações de um mesmo caso (comportamento com diferentes entradas)
  • Para regras de negócio que têm muitos casos específicos (ex: cálculos tributários)
  • Quando a lógica é a mesma, mas os dados variam
  • Para evitar duplicação de código em testes semelhantes

Benefícios:

  • Redução drástica de código duplicado
  • Facilidade para adicionar novos casos de teste
  • Documentação clara de todos os casos suportados
  • Facilita identificar quais casos específicos falharam
@ParameterizedTest
@CsvSource({
    "SP, 0.18",
    "RJ, 0.20",
    "MG, 0.17",
    "RS, 0.17"
})
void deveCalcularImpostoCorretoPorEstado(String estado, BigDecimal aliquotaEsperada) {
    // Arrange
    Produto produto = new Produto("Genérico", 100.0);

    // Act
    BigDecimal aliquota = impostoService.obterAliquota(estado, produto);

    // Assert
    assertEquals(aliquotaEsperada, aliquota);
}
Enter fullscreen mode Exit fullscreen mode

O JUnit 5 suporta múltiplas fontes de dados para testes parametrizados:

  • @ValueSource: para listas simples de valores (@ValueSource(ints = {1, 2, 3}))
  • @CsvSource: para pares ou tuplas de valores como mostrado acima
  • @EnumSource: para testar com valores de um enum
  • @MethodSource: para casos complexos onde os dados são gerados por um método
  • @ArgumentsSource: para implementações personalizadas de geradores de dados

Essas técnicas avançadas são particularmente úteis quando sua base de código cresce e você precisa manter um conjunto grande de testes. Elas ajudam a reduzir a duplicação, melhorar a legibilidade e facilitar a manutenção a longo prazo.

Integração Contínua: Executando testes unitários automaticamente

Os testes unitários devem ser executados:

  1. Localmente antes de commit/push
  2. Na CI em cada push para qualquer branch
  3. Antes de merge para branches principais

Este feedback rápido é possível graças à velocidade dos testes unitários.

Lições finais

Ham Vocke resume bem: "Uma vez que você pegar o jeito de escrever testes unitários, você se tornará cada vez mais fluente em escrevê-los. Substitua colaboradores externos por stubs, configure alguns dados de entrada, chame seu assunto sob teste e verifique se o valor retornado é o que você esperava."

Testes unitários são um investimento que:

  • ⏱️ Economiza tempo a longo prazo
  • 🔒 Permite refatorações com segurança
  • 📚 Documenta o comportamento esperado
  • 💻 Melhora o design do código
  • 🧠 Reduz a carga cognitiva ao desenvolver

Conclusão

Como resumido no artigo de referência: "O código de teste é tão importante quanto o código de produção. Dê a ele o mesmo nível de cuidado e atenção. 'Isso é apenas código de teste' não é uma desculpa válida para justificar código desleixado."

Ao dominar a arte dos testes unitários, você estabelece a base sólida para construir um sistema robusto e confiável, que pode evoluir com segurança ao longo do tempo.

No próximo passo da nossa maratona, abordaremos testes de integração - a camada intermediária da pirâmide de testes que verifica como seus componentes trabalham juntos.


Recursos recomendados

  • 📕 "Working Effectively with Unit Tests" por Jay Fields
  • 📗 "Test Driven Development: By Example" por Kent Beck
  • 📘 "Clean Code" por Robert C. Martin (capítulo sobre testes)
  • 🌐 Documentação oficial do JUnit 5
  • 🌐 Documentação oficial do Mockito
  • 🌐 Documentação oficial do AssertJ

Referências Bibliográficas

  1. Vocke, Ham. (2018). "The Practical Test Pyramid". Martin Fowler. Disponível em: https://martinfowler.com/articles/practical-test-pyramid.html
  2. Cohn, Mike. (2009). "Succeeding with Agile: Software Development Using Scrum". Addison-Wesley Professional.
  3. Fields, Jay. (2014). "Working Effectively with Unit Tests". Leanpub.
  4. Beck, Kent. (2002). "Test Driven Development: By Example". Addison-Wesley Professional.
  5. Martin, Robert C. (2008). "Clean Code: A Handbook of Agile Software Craftsmanship". Prentice Hall.
  6. Fowler, Martin. (2006). "Mocks Aren't Stubs". MartinFowler.com. Disponível em: https://martinfowler.com/articles/mocksArentStubs.html
  7. Spring Framework Documentation. "Testing". Disponível em: https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html
  8. JUnit 5 User Guide. Disponível em: https://junit.org/junit5/docs/current/user-guide/
  9. Mockito Reference Documentation. Disponível em: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
  10. AssertJ Documentation. Disponível em: https://assertj.github.io/doc/

Top comments (1)

Collapse
 
joaocarloscic profile image
João

Muito obrigado pelo esse conteúdo de excelência qualidade.