Project: InterimPlaza Recruitment Platform Developed by: GloryLabs for InterimPlaza Last Updated: October 10, 2025
This project uses a comprehensive testing strategy including:
backend/src/test/
├── java/nl/glorylabs/
│ ├── controller/ # Controller layer tests
│ │ ├── JobControllerTest.java ✅ 39 tests
│ │ ├── ApplicationControllerTest.java ✅ 34 tests
│ │ └── AuthControllerSecurityTest.java ✅ Security tests
│ │
│ ├── service/ # Service layer tests
│ │ ├── JobServiceTest.java ✅ 30+ tests
│ │ ├── ApplicationServiceTest.java ✅ 25+ tests
│ │ ├── AuthServiceTest.java ✅ 20+ tests
│ │ └── EmailServiceTest.java ✅ 15+ tests
│ │
│ ├── security/ # Security tests
│ │ ├── JwtTokenSecurityTest.java ✅ JWT validation
│ │ ├── RateLimitingSecurityTest.java ✅ Rate limiting
│ │ ├── SecurityHeadersTest.java ✅ Security headers
│ │ └── PasswordSecurityTest.java ✅ Password policies
│ │
│ ├── mapper/ # Mapper tests
│ │ ├── JobMapperTest.java ✅ DTO mapping
│ │ └── ApplicationMapperTest.java ✅ DTO mapping
│ │
│ ├── integration/ # Integration tests
│ │ ├── JobControllerIT.java ✅ End-to-end
│ │ ├── ApplicationControllerIT.java ✅ End-to-end
│ │ └── CucumberIT.java ✅ BDD runner
│ │
│ └── cv/ # CV module tests
│ └── service/CVProfileServiceTest.java ✅ CV tests
│
└── features/ # BDD feature files
├── authentication/
│ └── 01-user-authentication.feature
└── cv-management/
├── 01-cv-profile-management.feature
└── 02-cv-pdf-generation.feature
# Maven
./mvnw test
# Make
make test
# With coverage report
./mvnw clean test jacoco:report
# Unit tests only
./mvnw test -Dgroups=unit
# Integration tests only
./mvnw test -Dgroups=integration
# Controller tests
./mvnw test -Dtest="*ControllerTest"
# Service tests
./mvnw test -Dtest="*ServiceTest"
# Security tests
./mvnw test -Dtest="*SecurityTest"
# Single test class
./mvnw test -Dtest=JobControllerTest
# Single test method
./mvnw test -Dtest=JobControllerTest#getAllJobs_Success_ReturnsPageOfJobs
# Multiple test classes
./mvnw test -Dtest=JobControllerTest,ApplicationControllerTest
# Run Cucumber tests
./mvnw test -Dtest=CucumberIT
# Run specific feature
./mvnw test -Dcucumber.features=src/test/features/authentication/
# Run tests in Docker
docker-compose run --rm backend mvn test
# With coverage
docker-compose run --rm backend mvn clean test jacoco:report
package nl.glorylabs.recruitment.service;
import org.junit.jupiter.api.BeforeEach;
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.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
/**
* Unit tests for YourService
*
* Tests cover:
* - Feature 1
* - Feature 2
* - Error handling
*
* InterimPlaza Recruitment Platform - Developed by GloryLabs for InterimPlaza
*/
@ExtendWith(MockitoExtension.class)
class YourServiceTest {
@Mock
private YourRepository repository;
@InjectMocks
private YourService service;
private YourEntity testEntity;
@BeforeEach
void setUp() {
// Setup test data
testEntity = YourEntity.builder()
.id(1L)
.name("Test")
.build();
}
@Test
void methodName_scenario_expectedResult() {
// Given (Arrange)
when(repository.findById(1L)).thenReturn(Optional.of(testEntity));
// When (Act)
YourDto result = service.getById(1L);
// Then (Assert)
assertNotNull(result);
assertEquals("Test", result.getName());
verify(repository).findById(1L);
}
}
package nl.glorylabs.recruitment.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* Controller tests for YourController
*
* InterimPlaza Recruitment Platform - Developed by GloryLabs for InterimPlaza
*/
@WebMvcTest(YourController.class)
class YourControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private YourService service;
private YourDto testDto;
@BeforeEach
void setUp() {
testDto = new YourDto();
testDto.setId(1L);
testDto.setName("Test");
}
@Test
void getAll_Success_ReturnsData() throws Exception {
// Given
when(service.getAll()).thenReturn(List.of(testDto));
// When & Then
mockMvc.perform(get("/your-endpoint"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Test"));
}
@Test
@WithMockUser(roles = "ADMIN")
void create_Success_AsAdmin() throws Exception {
// Given
when(service.create(any(YourDto.class))).thenReturn(testDto);
// When & Then
mockMvc.perform(post("/your-endpoint")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(testDto)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Test"));
}
}
package nl.glorylabs.recruitment.integration;
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.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
/**
* Integration test for YourController
*
* InterimPlaza Recruitment Platform - Developed by GloryLabs for InterimPlaza
*/
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Transactional
class YourControllerIT {
@Autowired
private MockMvc mockMvc;
@Test
void endToEndFlow_Success() throws Exception {
// Test the full flow with real database
mockMvc.perform(get("/your-endpoint"))
.andExpect(status().isOk());
}
}
Feature: Your Feature Name
As a user role
I want to perform an action
So that I can achieve a goal
Background:
Given the system is running
And test data is loaded
Scenario: Happy path scenario
Given I am authenticated as a "USER"
When I request "/your-endpoint"
Then the response status should be 200
And the response should contain "expected data"
Scenario: Error scenario
Given I am not authenticated
When I request "/your-endpoint"
Then the response status should be 401
| Layer | Coverage | Status | |-------|----------|--------| | Controllers | 75% | 🟢 Good | | Services | 60% | 🟡 Moderate | | Repositories | 40% | 🟡 Moderate | | Security | 90% | 🟢 Excellent | | Mappers | 100% | 🟢 Excellent | | Overall | 25% | 🟡 Moderate |
# Generate JaCoCo report
./mvnw clean test jacoco:report
# View report
open target/site/jacoco/index.html
# Check coverage threshold (configured in pom.xml)
./mvnw jacoco:check
<jacoco.minimum.coverage>0.70</jacoco.minimum.coverage>
Pattern: methodName_scenario_expectedResult()
Good Examples:
✅ getAllJobs_Success_ReturnsPageOfJobs()
✅ createJob_Unauthorized_AsUser()
✅ getJobById_NotFound_ThrowsResourceNotFoundException()
Bad Examples:
❌ test1()
❌ testGetJobs()
❌ shouldReturnJobs()
@Test
void testMethod() {
// Given (Arrange) - Setup test data and mocks
when(repository.findById(1L)).thenReturn(Optional.of(entity));
// When (Act) - Execute the method being tested
YourDto result = service.getById(1L);
// Then (Assert) - Verify the results
assertNotNull(result);
assertEquals("expected", result.getValue());
}
Do Mock:
Don't Mock:
// Good - Multiple assertions for related properties
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals("Test", result.getName());
// Good - Specific error messages
assertThrows(ResourceNotFoundException.class,
() -> service.getById(999L),
"Should throw when resource not found");
// Bad - Single assertion in test
assertNotNull(result); // Test more!
// Bad - No verification of mocks
when(repository.save(any())).thenReturn(entity);
service.create(dto);
// Missing: verify(repository).save(any());
@BeforeEach
void setUp() {
// Create reusable test data
testEntity = Entity.builder()
.id(1L)
.name("Test")
.active(true)
.build();
}
// Use builders for flexibility
Entity.builder()
.id(1L)
.name("Custom Name")
.build();
// Test authenticated access
@Test
@WithMockUser(roles = "USER")
void testAsUser() { }
// Test admin access
@Test
@WithMockUser(roles = "ADMIN")
void testAsAdmin() { }
// Test unauthenticated access
@Test
void testUnauthenticated() { }
// Test with specific user
@Test
@WithMockUser(username = "john@example.com", roles = "USER")
void testAsSpecificUser() { }
// Test GET endpoint
mockMvc.perform(get("/jobs"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
// Test POST endpoint with CSRF
mockMvc.perform(post("/jobs")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isCreated());
// Test PUT endpoint
mockMvc.perform(put("/jobs/1")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(dto)))
.andExpect(status().isOk());
// Test DELETE endpoint
mockMvc.perform(delete("/jobs/1")
.with(csrf()))
.andExpect(status().isNoContent());
Group tests by functionality:
class JobServiceTest {
// ========== GET ALL JOBS TESTS ==========
@Test
void getAllJobs_Success() { }
@Test
void getAllJobs_EmptyList() { }
// ========== CREATE JOB TESTS ==========
@Test
void createJob_Success() { }
@Test
void createJob_Unauthorized() { }
// ========== ERROR HANDLING TESTS ==========
@Test
void createJob_InvalidInput() { }
}
Cause: Environment differences Solution:
# Use test profile
./mvnw test -Dspring.profiles.active=test
# Check environment variables
env | grep TEST
Cause: Race conditions, timing issues Solution:
// Bad - Time-dependent
Thread.sleep(1000);
// Good - Use awaitility
await().atMost(5, SECONDS)
.until(() -> repository.findById(1L).isPresent());
Cause: Wrong matcher or mock setup Solution:
// Bad - Exact match required
when(repository.findById(1L)).thenReturn(Optional.of(entity));
service.getById(2L); // Returns empty!
// Good - Use any() or verify argument
when(repository.findById(anyLong())).thenReturn(Optional.of(entity));
// Debug - Print actual arguments
verify(repository).findById(argThat(id -> {
System.out.println("Called with: " + id);
return true;
}));
Cause: Security enabled, no CSRF token Solution:
// Add .with(csrf()) to all mutations
mockMvc.perform(post("/jobs")
.with(csrf()) // Add this!
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated());
Cause: Missing ObjectMapper configuration Solution:
@Autowired
private ObjectMapper objectMapper;
String json = objectMapper.writeValueAsString(dto);
Project: InterimPlaza Recruitment Platform Developed by: GloryLabs for InterimPlaza Company: Mahmoud Consultancy B.V.
For questions or issues with tests:
Last Updated: October 10, 2025 Testing Guide v1.0
Reacties