DevOps ‐ Unit Tests - QuantumBitBR/API_5SEM GitHub Wiki
Testes unitários garantem a validação isolada e segura da lógica de negócio das classes de Service.
Cada método de negócio relevante deve possuir cobertura adequada.
-
Quem desenvolve o teste do service é o desenvolvedor da task
-
Caso o teste quebre durante o CI, quem corrige o teste, independente do teste quebrado, é o desenvolvedor dono do PR.
-
Confiança nas alterações de código.
-
Documentação viva da lógica implementada.
-
Detecção precoce de erros e regressões.
-
Facilidade para refatorações seguras.
-
Redução de falhas em produção.
-
JUnit 5 (
@Test
,@BeforeEach
) -
Mockito (
@Mock
,@InjectMocks
,when
,verify
) -
AssertJ ou asserções padrão do JUnit
-
Spring Boot Starter Test como dependência principal
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
Fase | O que fazer |
---|---|
Arrange | Configurar mocks, entradas e expectativas |
Act | Executar o método a ser testado |
Assert | Verificar se o comportamento esperado ocorreu |
package com.quantum.stratify.services;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import static org.mockito.Mockito.;
import static org.junit.jupiter.api.Assertions.;
class EmailServiceTest {
private JavaMailSender mailSender;
private EmailService emailService;
@BeforeEach
void setUp() {
mailSender = mock(JavaMailSender.class);
emailService = new EmailService(mailSender);
}
@Test
void deveEnviarEmailDeResetDeSenhaComSucesso() {
// Arrange
String destino = "[email protected]";
String novaSenha = "Senha123@";
ArgumentCaptor<SimpleMailMessage> captor = ArgumentCaptor.forClass(SimpleMailMessage.class);
// Act
emailService.enviarEmailSenhaResetada(destino, novaSenha);
// Assert
verify(mailSender, times(1)).send(captor.capture());
SimpleMailMessage mensagemEnviada = captor.getValue();
assertEquals(destino, mensagemEnviada.getTo()[0]);
assertEquals("Reset de senha - Stratify", mensagemEnviada.getSubject());
assertTrue(mensagemEnviada.getText().contains(novaSenha));
}
}
-
Isolar dependências usando
@Mock
. -
Não testar repositórios (
JpaRepository
) — responsabilidade dos testes de integração. -
Nome dos métodos de teste deve ser descritivo (em português ou inglês padronizado):
-
deveCalcularTempoMedioPorProjeto()
-
shouldReturnProjectListByUserId()
-
-
Evitar chamadas reais a banco de dados ou SMTP — apenas mocks.
flowchart TD
A[Início do Teste Unitário] --> B[Arrange: Configurações e Mocks]
B --> C[Act: Execução do método testado]
C --> D[Assert: Verificação dos resultados]
D --> E{Resultado esperado?}
E -- Sim --> F[Teste passa ✅]
E -- Não --> G[Teste falha ❌]
F --> H[Fim do Teste]
G --> H
-
EmailServiceTest
-
FatoEficienciaUserStoryServiceTest
-
UsuarioServiceTest
-
Maior confiança em releases.
-
Documentação viva da lógica implementada.
-
Facilidade na manutenção e expansão do código.
-
Redução de falhas em produção.
Para regras de negócio mais complexas, crie um teste para cada cenário relevante, assegurando cobertura ampla dos comportamentos possíveis. Caso exija dúvidas sobre a criação de cenários relevantes, consultar o PO.
-
As requisições aos serviços de backend sejam chamadas corretamente
-
Os parâmetros enviados estão completos e no formato esperado
-
A lógica de cada serviço esteja funcionando de forma isolada e previsível
-
Refatorar com segurança, sem medo de quebrar a integração frontend-backend
-
Vitest
-
vi.mock()
src/
├── services/
│ ├── AverageTimeService.ts
│ └── StatusService.ts
tests/
└── services/
├── AverageTimeService.spec.ts
└── StatusService.spec.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import AverageTimeService from '@/services/AverageTimeService'
import { api } from '@/services/apiConfig'
vi.mock('@/services/apiConfig', () => ({
api: { get: vi.fn() }
}))
describe('AverageTimeService', () => {
beforeEach(() => { vi.clearAllMocks() })
it('deve buscar tempo médio com os parâmetros corretos', async () => {
(api.get as any).mockResolvedValue({ data: { tempoMedio: 4.5 } })
const result = await AverageTimeService.getAverageTime(1, 2)
expect(api.get).toHaveBeenCalledWith('/fatoeficiencia/tempo-medio', {
params: { projetoId: 1, usuarioId: 2 }
})
expect(result.tempoMedio).toBe(4.5)
})
})
import { describe, it, expect, vi, beforeEach } from 'vitest'
import StatusService from '@/services/StatusService'
import { api } from '@/services/apiConfig'
vi.mock('@/services/apiConfig', () => ({
api: { get: vi.fn() }
}))
describe('StatusService', () => {
beforeEach(() => { vi.clearAllMocks() })
it('deve buscar percentual por status com projeto e usuário', async () => {
const mockData = [{ nomeStatus: 'Em Andamento', percentual: 70 }]
;(api.get as any).mockResolvedValue({ data: mockData })
const result = await StatusService.quantityPerStatus(10, 20)
expect(api.get).toHaveBeenCalledWith('/userStory/percentual-por-status', {
params: { projetoId: 10, usuarioId: 20 }
})
expect(result).toEqual(mockData)
})
})
npm install -D vitest @vitest/ui
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
coverage: {
reporter: ['text', 'json', 'html']
}
}
})
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui"
}
npm install -D vitest @vitest/ui @vitejs/plugin-vue
npm install -D @vitest/coverage-v8
npm run test