Let's say I am writing Spring integration tests for a REST service A. This service in turn hits another REST service B and gets a list of URIs to hit on REST service C. It is kind of auto-discovery pattern. I want to mock B and C responses using MockRestServiceServer.
Now the response from B is a list of URIs, they are all very similar, and for the sake of the example lets say my response from B is like so:
{
uris: ["/stuff/1.json", "/stuff/2.json", "/stuff/39.json", "/stuff/47.json"]
}
Simply service A will append each of them onto base URL for service C and make those requests.
Mocking B is easy since it is only 1 request.
Mocking C is a hassle as I would have to mock every single URI to appropriate mock response. I want to automate it!
So first I write my own matcher to match not a full URL, but part of it:
public class RequestContainsUriMatcher implements RequestMatcher {
private final String uri;
public RequestContainsUriMatcher(String uri){
this.uri = uri;
}
@Override
public void match(ClientHttpRequest clientHttpRequest) throws IOException, AssertionError {
assertTrue(clientHttpRequest.getURI().contains(uri));
}
}
This works fine as now I can do this:
public RequestMatcher requestContainsUri(String uri) {
return new RequestContainsUriMatcher(uri);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(/* I will get to response creator */);
Now all I need is a response creator that knows the full request URL and where the mock data sits (I will have it as json files in test resources folder):
public class AutoDiscoveryCannedDataResponseCreator implements ResponseCreator {
private final Function<String, String> cannedDataBuilder;
public AutoDiscoveryCannedDataResponseCreator(Function<String, String> cannedDataBuilder) {
this.cannedDataBuilder = cannedDataBuilder;
}
@Override
public ClientHttpResponse createResponse(ClientHttpRequest clientHttpRequest) throws IOException {
return withSuccess(cannedDataBuilder.apply(requestUri), MediaType.APPLICATION_JSON)
.createResponse(clientHttpRequest);
}
}
Now stuff is easy, I have to write a builder that takes request URI as a string and returns mock data, as a String! Brilliant!
public ResponseCreator withAutoDetectedCannedData() {
Function<String, String> cannedDataBuilder = new Function<String, String>() {
@Override
public String apply(String requestUri) {
//logic to get the canned data based on URI
return cannedData;
}
};
return new AutoDiscoveryCannedDataResponseCreator(cannedDataBuilder);
}
MockRestServiceServer.createServer(restTemplate)
.expect(requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
.andRespond(withAutoDetectedCannedData());
It works fine! .... For the first request.
After the first request (/stuff/1.json) my MockRestServiceServer responds with message "Assertion error: no further requests expected".
Basically, I can make as many requests to that MockRestServiceServer as there were .expect() calls on it. And since I had only 1 of them, only first request will go through.
Is there a way around it? I really don't want to mock service C 10 or 20 times...
If you look at the MockRestServiceServer class, it supports two 'expect()' methods. The first defaults to 'ExpectedCount.once()' but the second method allows you change this value
public ResponseActions expect(RequestMatcher matcher) {
return this.expect(ExpectedCount.once(), matcher);
}
public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) {
return this.expectationManager.expectRequest(count, matcher);
}
I found this ticket MockRestServiceServer should allow for an expectation to occur multiple times which outlines some options for second method.
In your case I think adding static import and using the manyTimes() method is neater code than the for loop
MockRestServiceServer
.expect(manyTimes(), requestContainsUri("/stuff"))
.andExpect(method(HttpMethod.GET))
Other options are
once();
manyTimes();
times(5);
min(2);
max(8);
between(3,6);