DEV Community

Cover image for Spring Boot Integration Testing
Mohammad Javed
Mohammad Javed

Posted on • Edited on

Spring Boot Integration Testing

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"));
}
Enter fullscreen mode Exit fullscreen mode

No more “it works on my machine” — with MockMvc, you’re testing the actual HTTP layer, response status codes, headers, and content.

@WebMvcTestvs @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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

@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

  1. 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.

  1. @MockBean gives compiler warning as below 'org.springframework.boot.test.mock.mockito.MockBean' is deprecated since version 3.4.0 and marked for removal

Assume below is the Integration Test

@WebMvcTest(JobController.class)
@Import(JobControllerITTest.TestConfig.class)
public class JobControllerITTest {
Enter fullscreen mode Exit fullscreen mode

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);
        }
    }
Enter fullscreen mode Exit fullscreen mode
  1. 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);
        }
Enter fullscreen mode Exit fullscreen mode

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);

     }
}
Enter fullscreen mode Exit fullscreen mode

//Domain

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Job {
    private Long id;
    private String title;
    private String description;
    private String location;
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

For More content on Spring Boot Read here.

Top comments (0)