This repository has been archived by the owner on Sep 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
/
RequestMatcherRule.java
316 lines (270 loc) · 10.6 KB
/
RequestMatcherRule.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package br.com.concretesolutions.requestmatcher;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import br.com.concretesolutions.requestmatcher.exception.RequestAssertionException;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import static org.junit.Assert.fail;
/**
* A wrapping rule around {@link MockWebServer} to ease mocking. This provides:
*
* <ul>
*
* <li>Fixtures setup: you can have a folder named fixtures in your resources and this rule will
* load them for you and put them in your response's body</li>
*
* <li>Status code setup: you can pass a response's status code when enqueuing</li>
*
* <li>Request assertions: when enqueuing you can add assertions to ensure that the request arrived
* is the one expected.</li>
*
* </ul> <p>
*
* This rule provides helper methods for enqueuing responses with the corresponding request's
* assertions. When using one of the mockResponse methods here the return is a {@link
* RequestMatchersGroup} instance for easy method chaining.
*
* @see TestRule
* @see MockWebServer
* @see RequestMatchersGroup
*/
public abstract class RequestMatcherRule implements TestRule {
private final MatcherDispatcher dispatcher = new MatcherDispatcher();
private final MockWebServer server;
private final String fixturesRootFolder;
private final Map<String, String> defaultHeaders = new HashMap<>();
private boolean guessMimeType = true;
RequestMatcherRule() {
this(new MockWebServer());
}
RequestMatcherRule(String fixturesRootFolder) {
this(new MockWebServer(), fixturesRootFolder);
}
RequestMatcherRule(MockWebServer server) {
this(server, "fixtures");
}
RequestMatcherRule(MockWebServer server, String fixturesRootFolder) {
this.server = server;
this.fixturesRootFolder = fixturesRootFolder;
}
// implemented by Unit and Instrumented dispatchers to find fixtures folder
protected abstract InputStream open(String path) throws IOException;
@Override
public Statement apply(Statement base, Description description) {
return server.apply(requestAssertionStatement(base), description);
}
/**
* Adds this header to all fixtures delivered by this rule.
*
* @param key Header key
* @param value Header value
* @return This for chaining
*/
public RequestMatcherRule withDefaultHeader(String key, String value) {
defaultHeaders.put(key, value);
return this;
}
/**
* Sets whether it should be tried to guess the proper mime type for the fixture from its file
* extension.
*
* @param guess True to enable guessing, false otherwise. Default true.
* @return This for chaining
*/
public RequestMatcherRule withGuessingMimeTypeFromFixtureExtension(boolean guess) {
guessMimeType = guess;
return this;
}
/**
* Returns the complete url of the relative path.
*
* @param path A relative path. For example: "/" would return http://localhost:<portnumber></portnumber>/
* @return An OkHttp URL
*/
public HttpUrl url(String path) {
return server.url(path);
}
/**
* Returns the wrapped {@link MockWebServer} instance
*/
public MockWebServer getMockWebServer() {
return server;
}
/**
* Used to read fixtures. This combines the fixturesRootFolder with the passed fixturePath to
* find the file to read.
*
* @param fixturePath Relative path
* @return The contents of the fixture
*/
public String readFixture(final String fixturePath) {
try {
return IOReader.read(open(fixturesRootFolder + "/" + fixturePath)) + "\n";
} catch (IOException e) {
throw new RuntimeException("Failed to read asset with path " + fixturePath, e);
}
}
/**
* Used to read fixtures. This combines the fixturesRootFolder with the passed fixturePath to
* find the file to read.
*
* @param fixturePath Relative path
* @return The contents of the fixture
*/
public byte[] readBinaryFixture(final String fixturePath) {
try {
return IOReader.readBinary(open(fixturesRootFolder + "/" + fixturePath));
} catch (IOException e) {
throw new RuntimeException("Failed to read asset with path " + fixturePath, e);
}
}
/**
* Adds a fixture to be used during the test case.
*
* @param response The {@link MockResponse} to return.
* @param matcher The {@link RequestMatchersGroup} instance to use for matching.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public <T extends RequestMatchersGroup> IfRequestMatches<T> addResponse(MockResponse response,
T matcher) {
return new IfRequestMatches<>(dispatcher.addFixture(response, matcher));
}
/**
* Adds a fixture to be used during the test case.
*
* @param response The {@link MockResponse} to return.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public IfRequestMatches<RequestMatchersGroup> addResponse(MockResponse response) {
return new IfRequestMatches<>(dispatcher.addFixture(response));
}
/**
* Adds a fixture to be used during the test case.
*
* @param fixturePath The path of the fixture inside the fixtures folder.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public IfRequestMatches<RequestMatchersGroup> addFixture(String fixturePath) {
return addFixture(200, fixturePath);
}
/**
* Adds a fixture to be used during the test case.
*
* @param fixturePath The path of the fixture inside the fixtures folder.
* @param statusCode The status of the mocked response.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public IfRequestMatches<RequestMatchersGroup> addFixture(int statusCode, String fixturePath) {
final MockResponse mockResponse = new MockResponse()
.setResponseCode(statusCode)
.setBody(readFixture(fixturePath));
if (guessMimeType) {
final String mimeType = IOReader.mimeTypeFromExtension(fixturePath);
if (mimeType != null) {
mockResponse.addHeader("Content-Type", mimeType);
}
}
if (!defaultHeaders.isEmpty()) {
for (String headerKey : defaultHeaders.keySet()) {
mockResponse.addHeader(headerKey, defaultHeaders.get(headerKey));
}
}
return addResponse(mockResponse);
}
/**
* Adds a fixture to be used during the test case.
*
* @param fixturePath The path of the fixture inside the fixtures fodler.
* @param matcher The {@link RequestMatchersGroup} instance to use for matching.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public <T extends RequestMatchersGroup> IfRequestMatches<T> addFixture(String fixturePath,
T matcher) {
return addResponse(new MockResponse()
.setResponseCode(200)
.setBody(readFixture(fixturePath)), matcher);
}
/**
* Adds a fixture to be used during the test case.
*
* @param statusCode The status of the mocked response.
* @param fixturePath The path of the fixture inside the fixtures fodler.
* @param matcher The {@link RequestMatchersGroup} instance to use for matching.
* @return A dsl instance {@link IfRequestMatches} for chaining
*/
public <T extends RequestMatchersGroup> IfRequestMatches<T> addFixture(int statusCode,
String fixturePath,
T matcher) {
return addResponse(new MockResponse()
.setResponseCode(statusCode)
.setBody(readFixture(fixturePath)), matcher);
}
/**
* Public class that eases the DSL reading of the chain. Sometimes all you want is to add a
* fixture without a matching. When you DO need to match the request, it is easier to read:
* "adds a fixture for the request that matches these constraints".
*
* @param <T> The type that extends from {@link RequestMatchersGroup}
*/
public static class IfRequestMatches<T extends RequestMatchersGroup> {
private final T group;
IfRequestMatches(T group) {
this.group = group;
}
public T ifRequestMatches() {
return group;
}
}
private void after(Exception exception, boolean success) throws Exception {
if (dispatcher.getAssertionException() != null) {
// if there was an exception in the test (for example a NPE) we print the
// RequestAssertionException before re-throwing the original exception.
// This might help debug where the test is failing. We can't simply add it as
// suppressed as it was added only in API 19.
if (exception != null) {
Logger.getLogger(RequestMatcherRule.class.getName())
.log(Level.SEVERE, "Test threw exception.", exception);
}
throw dispatcher.getAssertionException();
}
if (!success) {
if (exception != null) {
throw exception;
}
return;
}
if (dispatcher.size() != 0) {
try {
fail("There are fixtures that were not used.");
} catch (AssertionError e) {
throw new RequestAssertionException("Failed assertion.", e);
}
}
}
private Statement requestAssertionStatement(final Statement base) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
server.setDispatcher(dispatcher);
boolean success = false;
Exception exception = null;
try {
base.evaluate();
success = true;
} catch (Exception e) {
exception = e;
} finally {
after(exception, success);
}
}
};
}
}