Skip to content

Write Spring Boot Tests

Analyze $ARGUMENTS and write comprehensive unit or integration tests.

Steps

  1. Read the source class under test (controller, service, repository, etc.).
  2. Read the existing test class if one exists.
  3. Read the project's documentation for testing conventions, test runner (JUnit 5), and mocking approach (Mockito, MockMvc, etc.).
  4. Identify what needs to be tested based on the type of Spring artifact.
  5. Write or update the test class, following best practices for isolation, coverage, and clarity.

What to Test by Type

Controllers

  • HTTP endpoint mappings (GET, POST, PUT, DELETE)
  • Request/response status codes
  • Input validation (valid/invalid payloads)
  • Response body structure and content
  • Service method calls (mock services, verify interactions)
  • Exception handling and error responses
  • Security (authentication/authorization, if applicable)

Services

  • Public methods (business logic, edge cases)
  • Interactions with repositories (mock repositories, verify calls)
  • Exception handling (expected and unexpected cases)
  • State transitions (if service manages state)
  • Data transformation and mapping

Repositories

  • CRUD operations (save, find, update, delete)
  • Custom query methods
  • Query results for typical and edge cases
  • Transactional behavior (if relevant)

Configuration/Properties

  • Property binding with @ConfigurationProperties or @Value
  • Profile-specific configuration
  • Default values and overrides

Exception Handlers

  • Global exception mapping (e.g., @ControllerAdvice)
  • Error response structure and status codes
  • Handling of custom and standard exceptions

Testing Patterns

Controller Test Setup (Spring MVC - MockMvc)

java
// Standard REST controller test with MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUserWhenValidId() {
        UserDto user = new UserDto(1L, "Test");
        when(userService.getUserById(1L)).thenReturn(user);
        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(1L));
    }

    @Test
    void shouldUploadFile() {
        MockMultipartFile file = new MockMultipartFile(
            "file", "test.txt", "text/plain", "Hello World".getBytes()
        );
        mockMvc.perform(multipart("/api/files/upload").file(file))
            .andExpect(status().isOk());
    }
}

Controller Test Setup (Spring WebFlux - WebTestClient)

java
// Reactive REST controller test with WebTestClient
import org.springframework.test.web.reactive.server.WebTestClient;

@WebFluxTest(ReactiveUserController.class)
class ReactiveUserControllerTest {
    @Autowired
    private WebTestClient webTestClient;
    @MockBean
    private ReactiveUserService userService;

    @Test
    void shouldReturnUserMono() {
        UserDto user = new UserDto("1", "Reactive");
        when(userService.getUserById("1")).thenReturn(Mono.just(user));
        webTestClient.get().uri("/api/reactive/users/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.id").isEqualTo("1");
    }

    @Test
    void shouldReturnUserFlux() {
        UserDto user1 = new UserDto("1", "A");
        UserDto user2 = new UserDto("2", "B");
        when(userService.getAllUsers()).thenReturn(Flux.just(user1, user2));
        webTestClient.get().uri("/api/reactive/users")
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(UserDto.class)
            .hasSize(2)
            .contains(user1, user2);
    }

    @Test
    void shouldUploadFileReactive() {
        webTestClient.post()
            .uri("/api/reactive/files/upload")
            .bodyValue(new FileSystemResource("src/test/resources/test.txt"))
            .exchange()
            .expectStatus().isOk();
    }
}

Service Test Setup (Mockito)

java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    @InjectMocks
    private UserService userService;

    @Test
    void shouldReturnUserDtoWhenUserExists() {
        User user = new User(1L, "Test");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        UserDto dto = userService.getUserById(1L);
        assertEquals(1L, dto.getId());
    }
}

Repository Test Setup (DataJpaTest)

java
@DataJpaTest
class UserRepositoryTest {
    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndFindUser() {
        User user = new User(null, "Test");
        user = userRepository.save(user);
        Optional<User> found = userRepository.findById(user.getId());
        assertTrue(found.isPresent());
    }
}

Configuration Properties Test

java
@SpringBootTest(properties = "app.name=TestApp")
class AppPropertiesTest {
    @Autowired
    private AppProperties appProperties;

    @Test
    void shouldBindProperty() {
        assertEquals("TestApp", appProperties.getName());
    }
}

Exception Handler Test

java
@WebMvcTest
class GlobalExceptionHandlerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;

    @Test
    void shouldReturnNotFoundForMissingUser() {
        when(userService.getUserById(anyLong())).thenThrow(new ResourceNotFoundException("Not found"));
        mockMvc.perform(get("/api/users/99"))
            .andExpect(status().isNotFound())
            .andExpect(jsonPath("$.error").value("NOT_FOUND"));
    }
}

Rules

  • Place test classes in the same package structure under src/test/java as the source class.
  • Name test classes after the class under test, suffixed with Test (e.g., UserServiceTest).
  • Mock all dependencies except the class under test.
  • Use expressive assertions (AssertJ, Hamcrest, or JUnit assertions).
  • Test both typical and edge cases (nulls, errors, empty results).
  • For controllers, use MockMvc for HTTP request/response testing.
  • For services, use Mockito for mocking dependencies.
  • For repositories, use H2 in-memory DB and @DataJpaTest.
  • For configuration, use @SpringBootTest with test properties.
  • Avoid unnecessary use of @SpringBootTest for unit tests; prefer it for integration tests.
  • Use descriptive test method names: shouldReturnUserWhenValidId.
  • Clean up resources after tests if needed.
  • Run all tests in CI pipelines and fail builds on test failures.

Output

After writing tests, summarize:

  • Test class location
  • Number of test cases written
  • What behaviors are covered
  • Any edge cases or scenarios that are difficult to test automatically

References

Copyright © MSG Systems Romania AI Labs