Testes - ecosif-auth

Este documento descreve como escrever e executar testes no ecosif-auth.

Estrutura de Testes

Localização

Os testes devem estar no mesmo pacote que o código testado, no diretório src/test/java:

src/
├── main/
│   └── java/
│       └── io/ecosif/auth/
│           └── controller/
│               └── AuthController.java
└── test/
    └── java/
        └── io/ecosif/auth/
            └── controller/
                └── AuthControllerTest.java

Tipos de Testes

1. Testes Unitários

Testam classes isoladamente, usando mocks para dependências.

Exemplo: Teste de Service

package io.ecosif.auth.service;

import io.ecosif.auth.model.User;
import io.ecosif.auth.repository.UserRepository;
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.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void should_ReturnUser_When_UsernameExists() {
        // Given
        String username = "test@example.com";
        User expectedUser = new User();
        expectedUser.setUsername(username);
        expectedUser.setName("Test User");

        when(userRepository.findByUsername(username))
            .thenReturn(expectedUser);

        // When
        User result = userService.findUserByUsername(username);

        // Then
        assertThat(result).isNotNull();
        assertThat(result.getUsername()).isEqualTo(username);
        assertThat(result.getName()).isEqualTo("Test User");
        verify(userRepository, times(1)).findByUsername(username);
    }

    @Test
    void should_ReturnNull_When_UsernameNotExists() {
        // Given
        String username = "nonexistent@example.com";

        when(userRepository.findByUsername(username))
            .thenReturn(null);

        // When
        User result = userService.findUserByUsername(username);

        // Then
        assertThat(result).isNull();
        verify(userRepository, times(1)).findByUsername(username);
    }
}

2. Testes de Integração

Testam a interação entre múltiplos componentes.

Exemplo: Teste de Controller com MockMvc

package io.ecosif.auth.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.ecosif.auth.dto.LoginRequest;
import io.ecosif.security.jwt.TokenProvider;
import io.ecosif.auth.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
class AuthControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private UserService userService;

    @Test
    void should_ReturnBadRequest_When_UsernameIsEmpty() throws Exception {
        LoginRequest request = new LoginRequest();
        request.setUsername("");
        request.setPassword("password");

        mockMvc.perform(post("/api/auth/signin")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isBadRequest());
    }
}

3. Testes de Repositório

Testam acesso a dados usando banco em memória ou banco de teste.

Exemplo: Teste de Repository

package io.ecosif.auth.repository;

import io.ecosif.auth.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    void should_FindUser_When_UsernameExists() {
        // Given
        User user = new User();
        user.setUsername("test@example.com");
        user.setName("Test User");
        user.setPassword("password");
        user.setActive(true);
        entityManager.persistAndFlush(user);

        // When
        User found = userRepository.findByUsername("test@example.com");

        // Then
        assertThat(found).isNotNull();
        assertThat(found.getUsername()).isEqualTo("test@example.com");
    }
}

Executando Testes

Todos os Testes

mvn test

Teste Específico

mvn test -Dtest=AuthControllerTest

Classe Específica

mvn test -Dtest=UserServiceImplTest

Com Cobertura

mvn test jacoco:report

Relatório disponível em: target/site/jacoco/index.html

Configuração de Testes

application-test.yml

Crie um arquivo src/test/resources/application-test.yml:

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

app:
  auth:
    tokenSecret: test-secret-key-for-testing-only-minimum-64-characters-long
    tokenExpirationMsec: 1800000

logging:
  level:
    root: INFO
    io.ecosif.auth: DEBUG

Perfil de Teste

Use @ActiveProfiles("test") nos testes:

@SpringBootTest
@ActiveProfiles("test")
class AuthControllerIntegrationTest {
    // ...
}

Boas Práticas

1. Nomenclatura

Use nomes descritivos:

@Test
void should_ReturnUser_When_UsernameExists() {
    // ...
}

@Test
void should_ThrowException_When_UsernameIsNull() {
    // ...
}

2. Estrutura AAA

Organize testes em Arrange, Act, Assert:

@Test
void should_ReturnUser_When_UsernameExists() {
    // Arrange (Given)
    String username = "test@example.com";
    User expectedUser = new User();
    // ...

    // Act (When)
    User result = userService.findUserByUsername(username);

    // Assert (Then)
    assertThat(result).isNotNull();
    // ...
}

3. Isolamento

Cada teste deve ser independente:

@BeforeEach
void setUp() {
    // Reset mocks ou estado antes de cada teste
}

4. Mocks

Use mocks para dependências externas:

@Mock
private UserRepository userRepository;

@InjectMocks
private UserServiceImpl userService;

5. Assertions

Use AssertJ para assertions mais legíveis:

import static org.assertj.core.api.Assertions.assertThat;

assertThat(result).isNotNull();
assertThat(result.getUsername()).isEqualTo("test@example.com");
assertThat(list).hasSize(2).contains("item1");

Testes de Segurança

Teste de Autenticação

@Test
void should_RequireAuthentication_ForProtectedEndpoint() throws Exception {
    mockMvc.perform(get("/api/admin/docker-logs?service=ecosif-auth"))
        .andExpect(status().isUnauthorized());
}

Teste com Token Válido

@Test
void should_AllowAccess_When_ValidToken() throws Exception {
    // Criar token JWT válido
    String token = createValidToken();

    mockMvc.perform(get("/api/admin/docker-logs?service=ecosif-auth")
            .header("Authorization", "Bearer " + token))
        .andExpect(status().isOk());
}

Cobertura de Testes

Metas de Cobertura

Verificar Cobertura

mvn test jacoco:report

Troubleshooting

Testes Falhando

  1. Verifique os logs de erro
  2. Certifique-se de que todas as dependências estão mockadas
  3. Verifique se o banco de teste está configurado corretamente

Testes Lentos

  1. Use mocks ao invés de integrações reais quando possível
  2. Use banco em memória (H2) para testes de repositório
  3. Evite testes desnecessários de integração

Recursos