1

I am running JUnit5 with MockRestServiceServer and seeing that when I run multiple tests with async requests, the MockRestServiceServer in test A will see a request from test B and flag as unexpected.

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class MyTestClass {

  @Autowired
  private RestTemplate restTemplate;
  @Autowired
  private WebApplicationContext wac;

  private MockRestServiceServer mockServer;
  private MockMvc mockMvc;

  private URI asyncUri = URI.create("/asyncUrl");

  @BeforeEach
  public void setUp(){
    mockerServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
    mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
  }

  @AfterEach
  public void tearDown(){
    mockServer.verify();
    mockServer.reset();
  }

  @Test
  public void dontRunAsyncMethod(){
    mockServer.reset(); // Added trying to fix the issue
    mockServer.expect(ExpectedCount.never(), requestTo(asyncUri));

    URI uri = URI.create("/myApi?runAsyncMethod=false");
    mockMvc.perform(get(uri));
  }

  @Test
  public void doRunAsyncMethod(){
    mockServer.reset(); // Added trying to fix the issue

    // Side issue here - I have to use `between` rather than `times`
    // because mockServer validates before request happens
    mockServer.expect(ExpectedCount.between(0, 1), requestTo(asyncUri));

    URI uri = URI.create("/myApi?runAsyncMethod=true");
    mockMvc.perform(get(uri));
  }
}

When run separately, both tests pass. However, when I run them together, dontRunAsyncMethod will fail saying that no request was expected to /asyncUrl. Is there a way to separate the tests more so? Can I somehow ensure that all requests have completed before beginning the next test?

C Taylor
  • 117
  • 1
  • 9

1 Answers1

0

Your comment itself provides a hint:

// Side issue here - I have to use `between` rather than `times`
// because mockServer validates before request happens

That's actually the issue; that the test doesn't wait for the asynchronous call made in the background.

First off, this assertion should be corrected to times(1) because it wouldn't correct for the async call to be made 0 times in this test. By allowing it to assert that the call could be made 0 times, the async behavior's test is only passing by not specifying the correct behavior.

Now, the fact that this test completes before the call is made (and will fail with a correct assertion of times(1)) is directly related to the fact that if both tests are run, the other test is failing its assertion that the async call is not made. The cause of both is that the async test is not waiting for the async call to, well, to happen asynchronously: so the call fires off in the background at some point, but not till the one test has completed and JUnit has moved on to the other test.

The fix for this should be to ensure the test waits for the asynchronous processing. Hopefully something like this works (assuming the asynchronous mechanism in the implementation of /myApi is the same one that is talked about here):


  @Test
  public void doRunAsyncMethod(){
    mockServer.expect(ExpectedCount.times(1), requestTo(asyncUri));

    URI uri = URI.create("/myApi?runAsyncMethod=true");
    MvcResult mvcCall = mockMvc.perform(get(uri))
            .andExpect(status().isOk()) 
            .andExpect(request().asyncStarted()) 
            .andExpect(request().asyncResult("expected response here")) 
            .andReturn();
    mockMvc.perform(asyncDispatch(mvcCall)) 
            .andExpect(status().isOk()) 
            .andExpect(content().string("expected response here"));
  }

(You'll have to tweak the response bodies, and if your response status isn't OK then remove those lines or change them to the expected status. It's not immediately clear to me from the documentation which of these sets of expectations refers to the response from the call to /myApi that you are testing, and which refers to the response from mockServer.expect(ExpectedCount.times(1), requestTo(asyncUri)); – and I don't see the implementation of /myApi in the question, so the best I can do here is imitate the documentation. You might be able to look up the JavaDocs for asyncResult and see if there is a version with an empty/no response body, if your response isn't supposed to have content at all.)

Also, what if your non-async test causes the async call to be made, but because it doesn't wait for the async process that isn't supposed to happen in the first place, the test still passes the assertion that it is never called? (Not really "never" in the sense of for all time, but rather a count of 0 calls in the time the test is run!) To avoid this, we need to also assert that no async activity is run by that test's request:

  @Test
  public void dontRunAsyncMethod(){
    mockServer.expect(ExpectedCount.never(), requestTo(asyncUri));

    URI uri = URI.create("/myApi?runAsyncMethod=false");
    mockMvc.perform(get(uri))
            .andExpect(request().asyncNotStarted());
  }

As an aside, you can remove the lines at the start of each test (which are not needed with the verify and reset in the @AfterEach):

    // remove this:
    mockServer.reset(); // Added trying to fix the issue