When using MockRestServiceServer cannot precisely test number of service calls

Viewed 2937

I am coding some retry logic for a service call, and attempting to test that the Rest Template is attempting to hit the service a certain number of times, in a unit test. I am using the following code to perform the test.

MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build();
mockServer.expect(ExpectedCount.times(5), method(HttpMethod.GET))
  .andRespond(withServerError());

service.call();

I have the retry logic set to only make two attempts. The above test code requires that it occur five times, but the test always passes. In fact, the only way I can get this test to fail is by setting the expected count to one (anything less than the number of actual invocations). The same sort of problem occurs when I use ExpectedCount.min or ExpectedCount.between in that the test will only fail when actual invocations exceed expectation.

I need to be able to test for an exact number of service calls, preferably without the use of Mockito.

3 Answers

This is what finally worked for me, testing with a max attempts of 4:

MockRestServiceServer server;

@Before
public void setUp() {
    server = MockRestServiceServer.bindTo(restTemplate).build();
}

@After
public void serverVerify() {
    server.verify();
}

@Test
public void doWork_retryThenSuccess() throws Exception {
    final String responseBody = "<some valid response JSON>";
    final String url = BASE_URL + "/doWork";
    server.expect(requestTo(url))
          .andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
          .andRespond(ExceptionResponseCreator.withException(new SocketTimeoutException("first")));

    server.expect(requestTo(url))
          .andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
          .andRespond(ExceptionResponseCreator.withException(new IOException("second")));

    server.expect(requestTo(url))
          .andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
          .andRespond(ExceptionResponseCreator.withException(new RemoteAccessException("third")));

    server.expect(requestTo(url))
          .andExpect(MockRestRequestMatchers.method(HttpMethod.POST))
          .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));

    final MyResponseClass response = myService.call();

    assertThat(response, notNullValue());
    // other asserts here...
}

We are constrained to use Spring Test 5.0.10, which doesn't have MockRequestResponseCreators.withException() (method was added in 5.2.2). Borrowing from Spring 5.2.7 code, this works well:

package com.company.test;

import java.io.IOException;

import org.springframework.remoting.RemoteAccessException;
import org.springframework.test.web.client.ResponseCreator;
import org.springframework.test.web.client.response.MockRestResponseCreators;

public class ExceptionResponseCreator extends MockRestResponseCreators {
    public static ResponseCreator withException(IOException ex) {
        return request -> { throw ex; };
    }

    public static ResponseCreator withException(RemoteAccessException ex) {
        return request -> { throw ex; };
    }
}

You can create your own ResponseCreator with the logic you want. For example:

class DelegateResponseCreator implements ResponseCreator {
    private final ResponseCreator[] delegates;
    private int toExecute = 0;

    public DelegateResponseCreator(final ResponseCreator... delegates) {
        this.delegates = delegates;
    }

    @Override
    public ClientHttpResponse createResponse(final ClientHttpRequest request) throws IOException {
        ClientHttpResponse ret = this.delegates[this.toExecute % this.delegates.length].createResponse(request);
        this.toExecute++;

        return ret;
    }

}

This delegator executes the ResponseDelegates in order.

So you can mock the response for the call number you want

mockServer.expect(ExpectedCount.times(5), MockRestRequestMatchers.method(HttpMethod.GET))
            .andRespond(new DelegateResponseCreator(
                    MockRestResponseCreators.withServerError(), 
                    MockRestResponseCreators.withServerError(), 
                    MockRestResponseCreators.withServerError(), 
                    MockRestResponseCreators.withServerError(), 
                    MockRestResponseCreators.withSuccess()
                    ));

In this example, the first four calls will return a server error whereas the fifth will be a success.

You need to call mockServer.verify() after making all your requests to check if the expectations are met. Otherwise, you can get away with never making any requests.

Related