In today’s fast-paced development environment, ensuring code reliability is crucial. Spring Boot, a popular Java-based framework for creating stand-alone, production-grade applications, makes testing a first-class citizen in its ecosystem. Whether building REST APIs, data access layers, or service components, robust testing helps developers catch issues early, enhance maintainability, and build user trust.
This guide explores how to conduct adequate testing in Spring Boot, covering unit testing, integration testing, best practices, and tooling.
1. Why Testing Matters in Spring Boot
Spring Boot emphasizes convention over configuration, which accelerates development. However, rapid development without proper testing can lead to unstable software. Testing in Spring Boot ensures:
- Components behave as expected
- Dependencies are correctly wired
- External integrations work properly
- Refactoring doesn’t break existing functionality
Spring Boot provides various testing utilities out of the box, such as annotations (@SpringBootTest
, @MockitoBean
, @WebMvcTest
, etc.) and support for tools like JUnit 5, Mockito, and Testcontainers.
2. Types of Tests in Spring Boot
Prepare for the Class testing.
Create User
import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; import lombok.RequiredArgsConstructor; @Entity @Getter @Setter @Table(name = "\"user\"") // Quote the table name @RequiredArgsConstructor public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; public User(Long id, String name) { this.id = id; this.name = name; } }
Create Service
import com.example.entity.User; import com.example.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User getUserById(Long id) { return userRepository.findById(id) .orElseThrow(() -> new RuntimeException("User not found")); } public List<User> getAllUsers() { return userRepository.findAll(); } public User createUser(User user) { return userRepository.save(user); } public void deleteUser(Long id) { userRepository.deleteById(id); } }
Create Respository
import com.example.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> {}
Unit Testing
Unit tests focus on testing individual components in isolation. These are fast, easy to maintain, and great for catching issues early.
Example: Testing a Service Class
import com.example.controller.UserController; import com.example.entity.User; import com.example.service.UserService; 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 org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class UserControllerTest { @Mock private UserService userService; @InjectMocks private UserController userController; @Test void getUser_existingUser_returnsOk() { User user = new User(1L, "John Doe"); when(userService.getUserById(1L)).thenReturn(user); ResponseEntity<User> response = userController.getUser(1L); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(user, response.getBody()); } @Test void getUser_nonExistingUser_returnsNotFound() { when(userService.getUserById(1L)).thenReturn(null); ResponseEntity<User> response = userController.getUser(1L); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } }

Integration Testing
Integration tests verify how multiple components work together. These tests often interact with accurate databases or simulate APIs. It required a Docker Container on your machine.
Add Maven dependency
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.1</version> <scope>test</scope> </dependency>
Create a SQL script to initialize the Table User.
-- src/test/resources/init.sql CREATE TABLE "user" ( id SERIAL PRIMARY KEY, name VARCHAR(255) ); INSERT INTO "user" (name) VALUES ('Alice'); INSERT INTO "user" (name) VALUES ('Bob');
Directory
src/test/resources/init.sql
Using @SpringBootTest
with Testcontainers
package com.example.jobrunr.qc; import com.example.jobrunr.entity.User; import com.example.jobrunr.repository.UserRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers class UserIntegrationTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine") .withDatabaseName("testdb") .withUsername("testuser") .withPassword("testpassword").withInitScript("init.sql"); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); } @Autowired private TestRestTemplate restTemplate; @Autowired private UserRepository userRepository; @Test void getUser_existingUser_returnsOk() { User user = new User(null, "Jane Smith"); user = userRepository.save(user); ResponseEntity<User> response = restTemplate.getForEntity("/users/" + user.getId(), User.class); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(user.getName(), response.getBody().getName()); } }

3. Web Layer Testing
Testing the web layer allows developers to validate HTTP requests and responses without loading the full application context.
MockMvc with @WebMvcTest
package com.example.jobrunr.qc; import com.example.jobrunr.controller.UserController; import com.example.jobrunr.entity.User; import com.example.jobrunr.service.UserService; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(UserController.class) class UserControllerWebMvcTest { @Autowired private MockMvc mockMvc; @MockitoBean // Using the updated MockBean private UserService userService; @Autowired private ObjectMapper objectMapper; @Test void testGetUser() throws Exception { User user = new User(1L, "Charlie"); Mockito.when(userService.getUserById(1L)).thenReturn(user); mockMvc.perform(get("/users/1") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Charlie")); } }
@WebMvcTest
is perfect for isolating your MVC layer and running fast tests without hitting the database.
4. Data Layer Testing
Spring Boot simplifies testing JPA repositories with @DataJpaTest
. This loads only repository beans and configures an in-memory database by default (like H2).
@DataJpaTest class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test void testFindByEmail() { User user = new User(null, "Dana", "dana@example.com"); userRepository.save(user); User result = userRepository.findByEmail("dana@example.com"); assertNotNull(result); assertEquals("Dana", result.getName()); } }
5. Test Configuration and Profiles
Developers can configure separate profiles for testing using application-test.yml
or @ActiveProfiles("test")
. This lets you:
- Use different databases (e.g., H2 instead of PostgreSQL)
- Mock external services
- Override beans for test behavior
# application-test.yml spring: datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver username: sa password:
In your test class:
@ActiveProfiles("test") @SpringBootTest class UserServiceTest { ... }
6. Mocking and Dependency Injection
Spring Boot integrates seamlessly with Mockito, enabling developers to mock dependencies instead of real objects.
Tips:
- Use
@MockitoBean
to mock Spring-managed beans - Use
@Mock
for raw unit tests (non-Spring tests)
7. Test Coverage and Reporting
Tools like JaCoCo and SonarQube help visualize test coverage and identify untested paths. Adding JaCoCo to your pom.xml
is simple:
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.12</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
Run mvn jacoco:report
and locate your target/site/jacoco/index.html
report.


8. Best Practices for Testing in Spring Boot

Conclusion
Spring Boot offers powerful testing features that are right out of the box. From simple unit tests to complex integration tests with actual containers, the framework equips developers with all the tools to build reliable, maintainable applications.
By embracing testing as part of your development lifecycle, developers ship better software and gain confidence with every change. Start small, automate regularly, and grow your suite into a safety net that helps developers deliver with speed and quality.