Spring Boot Integration Testing
You have mastered Junit. You are Mockito maestro.But if you are not using Spring’s Integration Testing Tools, you are only fighting half the battle.
Let us dive into the world of Spring Integration Testing- where real applications meet real tests.
The Lonely World of Unit tests
For many Java developers, testing begins and ends with JUnit and Mockito. These tools are fantastic for isolating components and verifying behaviour in a controlled environment. But they are like testing a car’s engine on a workbench- it might run perfectly there, but what happens when you put it in the chassis and take it for a spin ?
That’s where Spring’s integration testing framework comes into play.
While you’ve been meticulously mocking every dependency, Spring has been quietly offering tools to test your application as a cohesive whole.
Spring Mock MVC: Your Application’s Mirror
Spring MockMvc allows you to test your controllers without deploying to a server, yet still exercise the full Spring MVC lifecycle. It’s like having a mini-version of your application running in a test bubble.
@Autowired
private MockMvc mockMvc;
@Test
public void testGetUsers() throws Exception {
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("John Doe"));
}
No more “it works on my machine” — with MockMvc, you’re testing the actual HTTP layer, response status codes, headers, and content.
@WebMvcTest
vs @SpringBootTest
: Choose Your Fighter
“Do I need both @WebMvcTest and @SpringBootTest?” I hear you ask. Let’s clear up the confusion:
@WebMvcTest
@WebMvcTest(UserController.class)
public class UserControllerTest {
// Your test code here
}
This annotation is focused on testing the web layer only. It:
- Disables full auto-configuration
- Only configures components relevant to MVC tests
- Scans only the specified controller
- Automatically configures MockMvc
- Think of
@WebMvcTest
as a lightweight champion — fast, nimble, and focused on a single task.
@SpringBootTest
@SpringBootTest
public class ApplicationIntegrationTest {
// Your test code here
}
This is the heavyweight champion that:
- Creates a full application context
- Loads the entire Spring application
- Can test interaction between all beans
- Provides a realistic testing environment
- So do you need both? No, Use either of them and not both in same Test class
@Import
and @ContextConfiguration
: The Context Jugglers
@Import
@WebMvcTest
@Import({SecurityConfig.class, CustomMapper.class})
public class ControllerTest {
// Your test code here
}
Think of @Import
as your VIP guest list. It tells Spring, “Hey, I know we’re only testing the web layer, but I really need these specific beans at the party.” Use it when you need to supplement your test slice with additional components.
@ContextConfiguration
@SpringBootTest
@ContextConfiguration(classes = {TestConfig.class})
public class CustomIntegrationTest {
// Your test code here
}
@ContextConfiguration
is more like rebuilding the venue according to your specifications. It gives you explicit control over how the application context is built, allowing you to specify configuration classes, XML configurations, or even context initializers.
Use @ContextConfiguration
when you need deeper control over your test environment setup.
When Integration Tests Go Wrong: Common Issues and Solutions
- The error:
java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration by searching packages upwards from the test.
So, if have the main configuration (class having @SpringBootApplication
) is under src/main/java/com/develop
then Integration Test should be under
src/test/java/com/develop
.
Spring Boot's test framework tries to find the main configuration class by searching upwards from the test class's package, but since your test is in the default package, it can't find.
-
@MockBean
gives compiler warning as below'org.springframework.boot.test.mock.mockito.MockBean'
is deprecated since version3.4.0
and marked for removal
Assume below is the Integration Test
@WebMvcTest(JobController.class)
@Import(JobControllerITTest.TestConfig.class)
public class JobControllerITTest {
then Mock the service class as below.
@Configuration
static class TestConfig {
@Bean
public JobService jobService() {
// Create and return a mock JobService
return org.mockito.Mockito.mock(JobService.class);
}
}
- Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException
: Error creating bean with name 'jobService': Unsatisfied dependency expressed through field 'auditRepository': No qualifying bean of type'com.develop.AuditRepository'
available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations:{@org.springframework.beans.factory.annotation.Autowired(required=true)}
Mock all the Repo which are used by Service class to avoid as shown below in the code.
@Configuration
static class TestConfig {
@Bean
public JobService jobService() {
return Mockito.mock(JobService.class);
}
@Bean
public AuditRepository auditRepository() {
return Mockito.mock(AuditRepository.class);
}
See below for complete code example
Code Example
Assuming basic controller like below with JobService calling a Mongo Repo- like AuditRepository
@RestController
public class JobController {
@Autowired
JobService jobService;
@GetMapping("/api/jobs")
public List<Job> getAllJobs(@RequestParam String query,HttpServletRequest request) {
return jobService.findByTitleLike(query);
}
}
//Domain
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Job {
private Long id;
private String title;
private String description;
private String location;
}
We have below working and compiled Integration Tests in (IntelliJ IDE)
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(JobController.class)
@Import(JobControllerITTest.TestConfig.class)
public class JobControllerITTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private JobService jobService;
private List<Job> mockJobs;
@Configuration
static class TestConfig {
@Bean
public JobService jobService() {
return Mockito.mock(JobService.class);
}
@Bean
public AuditRepository auditRepository() {
return Mockito.mock(AuditRepository.class);
}
@Bean
public JobRepository jobRepository() {
return Mockito.mock(JobRepository.class);
}
/** Add all the Repo which are used by Service class to avoid
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'Service': Unsatisfied dependency expressed through field 'auditRepository': No qualifying bean of type 'com.develop.AuditRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
*/
}
@BeforeEach
public void setup() {
// Create some mock jobs for testing
mockJobs = Arrays.asList(
createJob("1", "Java Developer", "Senior Java Developer position", "Remote"),
createJob("2", "Java Architect", "Experienced Java Architect needed", "New York")
);
// Configure mock behavior
when(jobService.fetchAll()).thenReturn(mockJobs);
when(jobService.findByTitleLike(Mockito.anyString())).thenReturn(mockJobs);
}
private Job createJob(String id, String title, String description, String location) {
Job job = new Job();
job.setId(id);
job.setTitle(title);
job.setDescription(description);
job.setLocation(location);
return job;
}
@Test
public void testGetAllJobs_Success() throws Exception {
// Given: The service returns jobs when searching for "Java"
when(jobService.findByTitleLike("Java")).thenReturn(mockJobs);
// When & Then: We call the API and verify the response
mockMvc.perform(get("/api/jobs")
.param("query", "Java")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$[0].title").value("Java Developer"))
.andExpect(jsonPath("$[1].title").value("Java Architect"))
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$.length()").value(2));
}
@Test
public void testGetAllJobs_EmptyResults() throws Exception {
// Given: The service returns empty list when searching for "Python"
when(jobService.findByTitleLike("Python")).thenReturn(Arrays.asList());
// When & Then: We call the API and verify we get an empty array
mockMvc.perform(get("/api/jobs")
.param("query", "Python")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$").isArray())
.andExpect(jsonPath("$.length()").value(0));
}
@Test
public void testGetAllJobs_MissingQueryParam() throws Exception {
// When & Then: We call the API without the required query parameter
mockMvc.perform(get("/api/jobs")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest());
}
@Test
public void testGetAllJobs_ServiceException() throws Exception {
// Given: The service throws an exception when searching for "Error"
when(jobService.findByTitleLike("Error")).thenThrow(new RuntimeException("Service error"));
// When & Then: We call the API and verify we get a server error
mockMvc.perform(get("/api/jobs")
.param("query", "Error")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isInternalServerError());
}
}
For More content on Spring Boot Read here.
Top comments (0)