Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MockRestServiceServer.verify() not working in a CompletableFuture [SPR-17266] #21799

Closed
spring-projects-issues opened this issue Sep 11, 2018 · 1 comment
Assignees
Labels
in: test Issues in the test module type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

member sound opened SPR-17266 and commented

When sending multiple async requests via RestTemplate using CompletableFuture, and when ignoring any exceptions (eg collecting only the requests that have been successful), the MockRestServiceServer.verify() method will not let the @Test fail!

Example:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MockRestServiceServerTest {
   @Autowired
   private MockMvc mvc;

   @Autowired
   private RestTemplate restTemplate;

   private MockRestServiceServer mockServer;

   @Before
   public void initmock() {
      this.mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
   }

   @After
   public void verify() {
      //this should let the test fail, but does not
      mockServer.verify();
   }

   @Test
   public void test() throws Exception {
      mockServer.expect(once(), requestTo("/remoteurl")).andRespond(withSuccess());
      mvc.perform(MockMvcRequestBuilders
            .post("/test"))
            .andExpect(status().isOk());
   }
} 

 

 

@RestController
public class TestServlet {
   @Autowired
   private RestTemplate restTemplate;

   @PostMapping("/test")
   public String test() {
      //simulate 3 concurrent requests
      List<String> requests = new ArrayList<>();
      requests.add("");
      requests.add("");
      requests.add("");

      final AtomicInteger counter = new AtomicInteger(1);

      List<CompletableFuture<ResponseEntity<String>>> futures =
            requests.stream()
                  .map(hostReq ->
                     CompletableFuture.supplyAsync(
                        () -> {
                           System.out.println("sending remote request: " + counter.getAndIncrement());
                           return restTemplate.postForEntity("/remoteurl", null, String.class);
                        })
                        .exceptionally(ex -> {
                           System.out.println("ignoring ex");
                           return null; //ignoring exceptions
                        }))
                  .collect(Collectors.toList());

      futures.stream()
            .map(CompletableFuture::join)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

      return "OK";
   }
} 

 

The rest template sends 3 requests out, and 2 exceptions are logged (because we set up the mock with once().

So far so good, but the MockRestServiceServer seems not to record the failed requests, and .verify() always passes! But it should fail because 3 requests have been send, instead of the expected one only.


Affects: 5.0.8

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Arguably verify() should fail if requests were made to a known URL but were rejected as unexpected. Normally of course the error should surface during request handling, unless it is ignored as in your case. So I'm scheduling this as an improvement.

As an aside this controller method looks like a prime candidate for using the WebClient. I don't know what your actual controller looks like, but based on the given sample code, it becomes something like this:

@RestController
public class MyController {

  @Autowired
  private WebClient webClient;

  @PostMapping("/test")
  public Flux<String> test() {

    //simulate 3 concurrent requests
    List<String> requests = new ArrayList<>();
    requests.add("");
    requests.add("");
    requests.add("");

    return Flux.fromIterable(requests).flatMap(request ->
            webClient.get().uri("/remoteurl")
                    .retrieve()
                    .bodyToMono(String.class));
  }
}

 
The return value from the controller is asynchronous too which means the handling is decoupled from the Servlet container thread, and not holding it up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants