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-issuemaster opened this issue Sep 11, 2018 · 1 comment
Assignees
Milestone

Comments

@spring-issuemaster
Copy link
Collaborator

@spring-issuemaster spring-issuemaster commented Sep 11, 2018

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-issuemaster
Copy link
Collaborator Author

@spring-issuemaster spring-issuemaster commented Oct 2, 2018

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
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.