Skip to content

Commit 9aa53ab

Browse files
committed
Add AsyncRestTemplate support to client-side MockMvc
Issue: SPR-1822
1 parent 045d735 commit 9aa53ab

File tree

5 files changed

+236
-13
lines changed

5 files changed

+236
-13
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.mock.http.client;
18+
19+
import org.springframework.core.task.AsyncListenableTaskExecutor;
20+
import org.springframework.http.HttpMethod;
21+
import org.springframework.http.client.AsyncClientHttpRequest;
22+
import org.springframework.http.client.ClientHttpRequest;
23+
import org.springframework.http.client.ClientHttpResponse;
24+
import org.springframework.mock.http.MockHttpOutputMessage;
25+
import org.springframework.util.concurrent.ListenableFuture;
26+
import org.springframework.util.concurrent.SettableListenableFuture;
27+
28+
import java.io.IOException;
29+
import java.net.URI;
30+
import java.util.concurrent.Callable;
31+
32+
/**
33+
* An extension of {@link MockClientHttpRequest} that also implements
34+
* {@link AsyncClientHttpRequest} by wraps the response in a "settable" future.
35+
*
36+
* @author Rossen Stoyanchev
37+
* @author Sam Brannen
38+
* @since 3.2
39+
*/
40+
public class MockAsyncClientHttpRequest extends MockClientHttpRequest implements AsyncClientHttpRequest {
41+
42+
43+
public MockAsyncClientHttpRequest() {
44+
}
45+
46+
public MockAsyncClientHttpRequest(HttpMethod httpMethod, URI uri) {
47+
super(httpMethod, uri);
48+
}
49+
50+
@Override
51+
public ListenableFuture<ClientHttpResponse> executeAsync() throws IOException {
52+
SettableListenableFuture<ClientHttpResponse> future = new SettableListenableFuture<ClientHttpResponse>();
53+
future.set(execute());
54+
return future;
55+
}
56+
57+
}

spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,25 @@
1616
package org.springframework.test.web.client;
1717

1818
import java.io.IOException;
19+
import java.io.OutputStream;
1920
import java.net.URI;
2021
import java.util.Iterator;
2122
import java.util.LinkedList;
2223
import java.util.List;
2324

25+
import org.springframework.http.HttpHeaders;
2426
import org.springframework.http.HttpMethod;
27+
import org.springframework.http.client.AsyncClientHttpRequest;
28+
import org.springframework.http.client.AsyncClientHttpRequestFactory;
2529
import org.springframework.http.client.ClientHttpRequest;
2630
import org.springframework.http.client.ClientHttpRequestFactory;
31+
import org.springframework.http.client.ClientHttpResponse;
2732
import org.springframework.test.web.client.match.MockRestRequestMatchers;
2833
import org.springframework.test.web.client.response.MockRestResponseCreators;
2934
import org.springframework.util.Assert;
35+
import org.springframework.util.concurrent.ListenableFuture;
36+
import org.springframework.util.concurrent.SettableListenableFuture;
37+
import org.springframework.web.client.AsyncRestTemplate;
3038
import org.springframework.web.client.RestTemplate;
3139
import org.springframework.web.client.support.RestGatewaySupport;
3240

@@ -80,11 +88,9 @@
8088
*/
8189
public class MockRestServiceServer {
8290

83-
private final List<RequestMatcherClientHttpRequest> expectedRequests =
84-
new LinkedList<RequestMatcherClientHttpRequest>();
91+
private final List<RequestMatcherClientHttpRequest> expectedRequests = new LinkedList<RequestMatcherClientHttpRequest>();
8592

86-
private final List<RequestMatcherClientHttpRequest> actualRequests =
87-
new LinkedList<RequestMatcherClientHttpRequest>();
93+
private final List<RequestMatcherClientHttpRequest> actualRequests = new LinkedList<RequestMatcherClientHttpRequest>();
8894

8995

9096
/**
@@ -104,12 +110,24 @@ private MockRestServiceServer() {
104110
*/
105111
public static MockRestServiceServer createServer(RestTemplate restTemplate) {
106112
Assert.notNull(restTemplate, "'restTemplate' must not be null");
107-
108113
MockRestServiceServer mockServer = new MockRestServiceServer();
109114
RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory();
110-
111115
restTemplate.setRequestFactory(factory);
116+
return mockServer;
117+
}
112118

119+
/**
120+
* Create a {@code MockRestServiceServer} and set up the given
121+
* {@code AsyRestTemplate} with a mock {@link AsyncClientHttpRequestFactory}.
122+
*
123+
* @param asyncRestTemplate the AsyncRestTemplate to set up for mock testing
124+
* @return the created mock server
125+
*/
126+
public static MockRestServiceServer createServer(AsyncRestTemplate asyncRestTemplate) {
127+
Assert.notNull(asyncRestTemplate, "'asyncRestTemplate' must not be null");
128+
MockRestServiceServer mockServer = new MockRestServiceServer();
129+
RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory();
130+
asyncRestTemplate.setAsyncRequestFactory(factory);
113131
return mockServer;
114132
}
115133

@@ -171,7 +189,6 @@ private String getVerifyMessage() {
171189
sb.append(request.toString()).append("\n");
172190
}
173191
}
174-
175192
return sb.toString();
176193
}
177194

@@ -180,12 +197,22 @@ private String getVerifyMessage() {
180197
* Mock ClientHttpRequestFactory that creates requests by iterating
181198
* over the list of expected {@link RequestMatcherClientHttpRequest}'s.
182199
*/
183-
private class RequestMatcherClientHttpRequestFactory implements ClientHttpRequestFactory {
200+
private class RequestMatcherClientHttpRequestFactory
201+
implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
184202

185203
private Iterator<RequestMatcherClientHttpRequest> requestIterator;
186204

187205
@Override
188206
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
207+
return createRequestInternal(uri, httpMethod);
208+
}
209+
210+
@Override
211+
public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException {
212+
return createRequestInternal(uri, httpMethod);
213+
}
214+
215+
private RequestMatcherClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
189216
Assert.notNull(uri, "'uri' must not be null");
190217
Assert.notNull(httpMethod, "'httpMethod' must not be null");
191218

@@ -201,9 +228,8 @@ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IO
201228
request.setMethod(httpMethod);
202229

203230
MockRestServiceServer.this.actualRequests.add(request);
204-
205231
return request;
206232
}
207233
}
208234

209-
}
235+
}

spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121

2222
import org.springframework.http.client.ClientHttpResponse;
23+
import org.springframework.mock.http.client.MockAsyncClientHttpRequest;
2324
import org.springframework.mock.http.client.MockClientHttpRequest;
2425
import org.springframework.util.Assert;
2526

@@ -33,7 +34,7 @@
3334
* @author Rossen Stoyanchev
3435
* @since 3.2
3536
*/
36-
class RequestMatcherClientHttpRequest extends MockClientHttpRequest implements ResponseActions {
37+
class RequestMatcherClientHttpRequest extends MockAsyncClientHttpRequest implements ResponseActions {
3738

3839
private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>();
3940

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.test.web.client.samples;
17+
18+
import org.junit.Before;
19+
import org.junit.Test;
20+
import org.springframework.core.io.ClassPathResource;
21+
import org.springframework.core.io.Resource;
22+
import org.springframework.http.HttpMethod;
23+
import org.springframework.http.MediaType;
24+
import org.springframework.http.ResponseEntity;
25+
import org.springframework.test.web.Person;
26+
import org.springframework.test.web.client.MockRestServiceServer;
27+
import org.springframework.util.concurrent.ListenableFuture;
28+
import org.springframework.web.client.AsyncRestTemplate;
29+
30+
import static org.junit.Assert.assertTrue;
31+
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
32+
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
33+
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
34+
35+
/**
36+
* Examples to demonstrate writing client-side REST tests with Spring MVC Test.
37+
* While the tests in this class invoke the RestTemplate directly, in actual
38+
* tests the RestTemplate may likely be invoked indirectly, i.e. through client
39+
* code.
40+
*
41+
* @author Rossen Stoyanchev
42+
*/
43+
public class SampleAsyncTests {
44+
45+
private MockRestServiceServer mockServer;
46+
47+
private AsyncRestTemplate restTemplate;
48+
49+
50+
@Before
51+
public void setup() {
52+
this.restTemplate = new AsyncRestTemplate();
53+
this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
54+
55+
}
56+
57+
@Test
58+
public void performGet() throws Exception {
59+
60+
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
61+
62+
this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
63+
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
64+
65+
@SuppressWarnings("unused")
66+
ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
67+
68+
// We are only validating the request. The response is mocked out.
69+
// person.getName().equals("Ludwig van Beethoven")
70+
// person.getDouble().equals(1.6035)
71+
72+
this.mockServer.verify();
73+
}
74+
75+
@Test
76+
public void performGetAsync() throws Exception {
77+
78+
String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}";
79+
80+
this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
81+
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
82+
83+
@SuppressWarnings("unused")
84+
ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
85+
86+
// person.getName().equals("Ludwig van Beethoven")
87+
// person.getDouble().equals(1.6035)
88+
89+
this.mockServer.verify();
90+
}
91+
92+
@Test
93+
public void performGetWithResponseBodyFromFile() throws Exception {
94+
95+
Resource responseBody = new ClassPathResource("ludwig.json", this.getClass());
96+
97+
this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET))
98+
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
99+
100+
@SuppressWarnings("unused")
101+
ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42);
102+
103+
// hotel.getId() == 42
104+
// hotel.getName().equals("Holiday Inn")
105+
106+
this.mockServer.verify();
107+
}
108+
109+
@Test
110+
public void verify() {
111+
112+
this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET))
113+
.andRespond(withSuccess("1", MediaType.TEXT_PLAIN));
114+
115+
this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET))
116+
.andRespond(withSuccess("2", MediaType.TEXT_PLAIN));
117+
118+
this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET))
119+
.andRespond(withSuccess("4", MediaType.TEXT_PLAIN));
120+
121+
this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET))
122+
.andRespond(withSuccess("8", MediaType.TEXT_PLAIN));
123+
124+
@SuppressWarnings("unused")
125+
ListenableFuture<ResponseEntity<String>> result = this.restTemplate.getForEntity("/number", String.class);
126+
// result == "1"
127+
128+
result = this.restTemplate.getForEntity("/number", String.class);
129+
// result == "2"
130+
131+
try {
132+
this.mockServer.verify();
133+
}
134+
catch (AssertionError error) {
135+
assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed"));
136+
}
137+
}
138+
}

spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ public void performGet() throws Exception {
6161
@SuppressWarnings("unused")
6262
Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42);
6363

64-
// person.getName().equals("Ludwig van Beethoven")
65-
// person.getDouble().equals(1.6035)
64+
// We are only validating the request. The response is mocked out.
65+
// hotel.getId() == 42
66+
// hotel.getName().equals("Holiday Inn")
6667

6768
this.mockServer.verify();
6869
}

0 commit comments

Comments
 (0)