javaspringintegration-testingspring-webmvc

MockMvc Test doesnt work when internal API is called


I am developing a Spring Boot application/library where i have a Service that sends a request to a an API. When in integration Test the Entry Point into the Service is an /integration Endpoint. The following Code is the mvp to reproduce the error

Main Service:

@Service
@RequiredArgsConstructor
public class CalculationService {

  private final RestClient restClient;

  public CalculationResponse calculate(CalculationRequest request){
    return restClient.post().uri("/service/calculate")
        .body(request)
        .retrieve()
        .toEntity(CalculationResponse.class).getBody();

  }
}

Main Controller

@RestController
@RequestMapping("/service")
public class MockApi {


  @PostMapping("/calculate")
  public CalculationResponse calculate(@RequestBody CalculationRequest calculationRequest) {
    CalculationResponse calculationResponse = new CalculationResponse();
    calculationResponse.setSummary(calculationRequest.getName()+ "----" + calculationRequest.getValue());
    return calculationResponse;
  }
}

The Entry Point for my Integration Test

@RestController
@RequestMapping
@RequiredArgsConstructor
public class TestController {

  private final CalculationService calculationService;

  @PostMapping("/integration")
  public CalculationResponse integration(@RequestBody CalculationRequest request) {
    return calculationService.calculate(request);
  }

}

RestClient Configuration

@Configuration
public class CalculationConfiguration {


  @Bean
  public RestClient restClient() {
    return RestClient.builder()
        .baseUrl("http://localhost:8080")
        .build();
  }

}

Integration Test

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension.class)
public class IntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private ObjectMapper objectMapper;

  @Test
  public void thatSummaryIsReturned() throws Exception {

    CalculationRequest request = new CalculationRequest("Test", "Value");
    mockMvc.perform(MockMvcRequestBuilders.post("/integration")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isOk())
        .andReturn();
  }

}

When running the test i receive following error:

Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/service/calculate": null jakarta.servlet.ServletException: Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8080/service/calculate": null at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1022) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72) at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)

When i change the endpoint in my integration test directly to /service/calculate it works. I tried to debug but when having /integration as path in the test i dont even react the other endpoint. Has anyone an idea why this is the case ?

Edit: When the Application runs in the Background it works. So it seems that there is something missing in my TestContext ...


Solution

  • The issue occurs because you are using @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT), which starts your application on a random port. However, your RestClient is explicitly configured to use http://localhost:8080, leading to a port mismatch. Since the application is not actually running on port 8080 during the test, requests fail with an I/O error when trying to reach http://localhost:8080/service/calculate.

    One solution is to start the test on a defined port:

    @SpringBootTest(
         webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, 
         properties = "server.port=8080"
    )