├── .gitignore ├── LICENSE ├── README.MD ├── pgssoft-logo.png ├── pom.xml └── src ├── main └── java │ ├── com │ └── pgssoft │ │ └── httpclient │ │ ├── Action.java │ │ ├── Condition.java │ │ ├── HttpClientMock.java │ │ ├── HttpClientMockBuilder.java │ │ ├── HttpClientResponseBuilder.java │ │ ├── HttpClientVerify.java │ │ ├── HttpClientVerifyBuilder.java │ │ ├── MockedServerResponse.java │ │ ├── NoMatchingRuleException.java │ │ └── internal │ │ ├── HttpMethods.java │ │ ├── HttpResponseProxy.java │ │ ├── ParameterValue.java │ │ ├── PeekSubscriber.java │ │ ├── UrlConditions.java │ │ ├── UrlParams.java │ │ ├── UrlParamsMatcher.java │ │ ├── action │ │ ├── ActionBundle.java │ │ ├── SetBodyStringAction.java │ │ ├── SetHeaderAction.java │ │ ├── SetStatusAction.java │ │ └── ThrowExceptionAction.java │ │ ├── condition │ │ ├── BodyCondition.java │ │ ├── HeaderCondition.java │ │ └── MethodCondition.java │ │ ├── debug │ │ └── Debugger.java │ │ └── rule │ │ ├── Rule.java │ │ └── RuleBuilder.java │ └── module-info.java └── test └── java └── com └── pgssoft └── httpclient ├── DebuggerTest.java ├── HttpClientMockAsyncTest.java ├── HttpClientMockBuilderTest.java ├── HttpClientResponseBuilderTest.java ├── HttpClientVerifyTest.java ├── HttpResponseMatchers.java ├── TestRequests.java ├── UrlConditionsTest.java └── internal └── UrlParamsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | build 4 | target -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 PGS Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ![pgssoft-logo.png](pgssoft-logo.png) 2 | 3 | # HttpClientMock 4 | 5 | HttpClientMock is a library for mocking [Java HttpClient](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html). It has an intuitive fluent API for defining client behaviour and verifing number of made requests. 6 | 7 | * [Installation](#installation) 8 | * [Requirements](#requirements) 9 | * [Usage](#usage) 10 | * [Request matching](#request-matching) 11 | * [Define response](#define-response) 12 | * [Verification](#verification) 13 | * [Examples](#examples) 14 | * [License](#license) 15 | * [About](#about) 16 | * [Follow us](#follow-us) 17 | 18 | ## Installation 19 | 20 | HttpClientMock is available in Maven Central Repository. 21 | ``` 22 | 23 | com.pgs-soft 24 | HttpClientMock 25 | 1.0.0 26 | 27 | ``` 28 | 29 | ## Requirements 30 | 31 | * Java 11 32 | 33 | ## Usage 34 | 35 | #### Record 36 | Working with HttpClientMock starts with defining client behaviour. Before code under tests starts HttpClientMock must know how to respond to every request. 37 | ``` 38 | HttpClientMock httpClientMock = new HttpClientMock(); 39 | httpClientMock.onGet("http://localhost/login") 40 | .withParameter("user","john") 41 | .doReturn("Ok"); 42 | httpClientMock.onPost("http://localhost/login").doReturnStatus(501); 43 | ``` 44 | 45 | #### Replay 46 | Code under test starts and uses HttpClientMock with defined behaviour. 47 | ``` 48 | var get = HttpRequest.newBuilder(URI.create("http://localhost/login?user:john")).GET().build(); 49 | var post = HttpRequest.newBuilder(URI.create("http://localhost/login")).POST(noBody()).build(); 50 | httpClient.send(get, ofString()); // returns response with body "Ok" 51 | httpClient.send(post, ofString()); // returns response with status 501 52 | ``` 53 | 54 | #### Verify 55 | When code under test finishes, HttpClientMock allows to check number of made request. It is possible to use the same set of conditions as for defining mock behaviour. 56 | ``` 57 | httpClientMock.verify().get("http://localhost/login").withParameter("user","john").called() 58 | httpClientMock.verify().post("http://localhost/login").notCalled() 59 | ``` 60 | 61 | 62 | ## Request matching 63 | 64 | ### HTTP method 65 | HttpClientMock supports all Http methods. 66 | ``` 67 | httpClientMock.onGet().doReturn("get"); 68 | httpClientMock.onPost().doReturn("post"); 69 | httpClientMock.onPut().doReturn("put"); 70 | httpClientMock.onDelete().doReturn("delete"); 71 | httpClientMock.onOptions().doReturn("options"); 72 | httpClientMock.onHead().doReturn("head"); 73 | ``` 74 | ### URL 75 | Every `onGet()`, `onPost()`, .... method accept URL. It is possible to write: 76 | ``` 77 | httpClientMock.onGet("http://localhost/login?user=john").doReturnStatus(200); 78 | ``` 79 | which is equal to 80 | ``` 81 | httpClientMock.onGet() 82 | .withHost("httt://locahost") 83 | .withPath("/login") 84 | .withParameter("user","john") 85 | .doReturnStatus(200); 86 | ``` 87 | 88 | It is possible to define default host using HttpClientMock constructor, so later methods can accept relative URL-s. 89 | ``` 90 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 91 | httpClientMock.onGet("/login").doReturn("ok"); 92 | httpClientMock.onPost("/edit?user=john").doReturnStatus(200); 93 | 94 | httpClientMock.onGet("http://www.google.com").doReturn("Google") // Absolute paths still work. 95 | ``` 96 | 97 | ### Host, path, parameters, reference conditions 98 | It is possible to define each part of url separately. 99 | ``` 100 | httpClientMock.onGet() 101 | .withHost("httt://locahost") 102 | .withPath("/login") 103 | .withParameter("user","john") 104 | .withReference("edit") 105 | .doReturnStatus(200); 106 | ``` 107 | 108 | ### Header condition 109 | ``` 110 | httpClientMock.onGet("http://localhost/login") 111 | .withHeader("tracking","123") 112 | .doReturn("ok"); 113 | ``` 114 | 115 | ### Body condition 116 | ``` 117 | httpClientMock.onGet("http://localhost/login") 118 | .withBody("tracking",containsString(123)) 119 | .doReturn("ok"); 120 | ``` 121 | 122 | ### Custom condition 123 | ``` 124 | Condition fooCondition = request -> request.getUri().contains("foo"); 125 | httpClientMock.onGet("http://localhost/foo/bar") 126 | .with(fooCondition) 127 | .doReturn("yes"); 128 | ``` 129 | 130 | ### Matchers 131 | Every condition method accepts [Hamcrest Matcher](https://github.com/hamcrest/JavaHamcrest) which allows to define custom conditions on requests. 132 | ``` 133 | httpClientMock.onGet("http://localhost") 134 | .withPath(containsString("login")) 135 | .withParameter("user",equalToIgnoringCase("John)") 136 | .reference(not(equalTo("edit"))); 137 | ``` 138 | 139 | ### Multiple matching rules 140 | If request matches more then one rule, then last defined one is used. 141 | 142 | ### None rule matche 143 | If request doesn't matche any rule, HttpClientMock return response with status 404. 144 | 145 | ## Define response 146 | 147 | ### Response 148 | Response with provided body and status 200. 149 | ``` 150 | httpClientMock.onGet("http://localhost").doReturn("my response") 151 | ``` 152 | ### Status 153 | Response with empty body and provided status 154 | ``` 155 | httpClientMock.onGet("http://localhost").doReturnStatus(300) 156 | httpClientMock.onGet("http://localhost").doReturn("Overloaded").withStatus("500"); 157 | ``` 158 | ### Exception 159 | Instead of returning response it throws defined exception. 160 | ``` 161 | httpClientMock.onGet("http://localhost").doThrowException(new IOException()); 162 | ``` 163 | ### Custom action 164 | ``` 165 | Action echo r -> { 166 | HttpEntity entity = ((HttpEntityEnclosingRequestBase) r.getHttpRequest()).getEntity(); 167 | BasicHttpResponse response = new BasicHttpResponse(new ProtocolVersion("http", 1, 1), 200, "ok"); 168 | response.setEntity(entity); 169 | return response; 170 | }; 171 | httpClientMock.onGet("http://localhost").doAction(echo); 172 | ``` 173 | ### Response header 174 | ``` 175 | httpClientMock.onPost("/login").doReturn("foo").withHeader("tracking", "123") 176 | ``` 177 | ### Response status 178 | ``` 179 | httpClientMock.onPost("/login?user=bar").doReturn("Wrong user").withStatus(403) 180 | ``` 181 | 182 | ### JSON 183 | Response with provided body, status 200 and content type "application/json" 184 | ``` 185 | httpClientMock.onPost("/login").doReturnJSON("{foo:1}"); 186 | ``` 187 | 188 | ### XML 189 | Response with provided body, status 200 and content type "application/xml" 190 | ``` 191 | httpClientMock.onPost("/login").doReturnXML("bar"); 192 | ``` 193 | 194 | ### Multiple actions 195 | It is possible to add multiple actions to one rule. Every call will use next action until last is reached. 196 | ``` 197 | httpClientMock.onPut("/addUser") 198 | .doReturn("ok"); 199 | .doReturnStatus(500); 200 | 201 | var req = HttpRequest.newBuilder(URI.create("http://localhost/addUser")).PUT(noBody()).build(); 202 | httpClientMock.send(req, ofString()); //returns "ok" 203 | httpClientMock.send(req, ofString()); //returns status 500 204 | httpClientMock.send(req, ofString()); //returns status 500 205 | ``` 206 | 207 | 208 | ## Verification 209 | HttpClientMock allows to check how many calls were made. Verification supports the same set of conditions us rule defining. 210 | ``` 211 | httpClientMock.verify().get("http://localhost").called(); 212 | 213 | httpClientMock.verify().get("http://localhost/login") 214 | .withParameter("user","john") 215 | .called(); 216 | 217 | httpClientMock.verify().get("http://localhost/login") 218 | .withParameter("user","Ben") 219 | .notCalled(); 220 | 221 | httpClientMock.verify().delete().notCalled(); 222 | 223 | httpClientMock.verify().get().called(greaterThanOrEqualTo(1)); 224 | 225 | ``` 226 | # Examples 227 | ## Example 1 228 | ``` 229 | // DEFINE BEHAVIOUR 230 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 231 | httpClientMock.onGet("/login?user=john").doReturnJSON("{permission:1}"); 232 | httpClientMock.onPost("/edit") 233 | .withParameter("user","John") 234 | .doReturn("ok") 235 | .doReturnStatus(503); 236 | 237 | // EXECUTION 238 | // request to http://localhost:8080/login?user=john returns JSON {permission:1} 239 | // first request to http://localhost:8080/edit?user=john returns message "ok" 240 | // second request to http://localhost:8080/edit?user=john returns request with status 503 241 | 242 | // VERIFICATION 243 | httpClientMock.verify().get("/login?user=john").called(); 244 | httpClientMock.verify().post("/edit?user=john").called(2); 245 | httpClientMock.verify().delete().notCalled(); 246 | ``` 247 | 248 | 249 | ## Example 2 250 | ``` 251 | // DEFINE BEHAVIOUR 252 | HttpClientMock httpClientMock = new HttpClientMock(); 253 | httpClientMock.onGet("http://localhost:8080/login").doReturn("Missing parameter user").withStatus(400); 254 | httpClientMock.onGet("http://localhost:8080/login") 255 | .withParameter("user","JJohn") 256 | .doReturn("Wrong user name").withStatus(403); 257 | httpClientMock.onGet("http://localhost:8080/login") 258 | .withParameter("user","John") 259 | .doReturn("ok"); 260 | 261 | // EXECUTION 262 | // request to http://localhost:8080/login?user=john returns message "ok" 263 | 264 | // VERIFICATION 265 | httpClientMock.verify().get("/login?user=john").called(); 266 | ``` 267 | 268 | ## License 269 | 270 | The project is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 271 | 272 | ## About 273 | 274 | The project maintained by [software development agency](https://www.pgs-soft.com/) [PGS Software](https://www.pgs-soft.com/). 275 | See our other [open-source projects](https://github.com/PGSSoft) or [contact us](https://www.pgs-soft.com/contact-us/) to develop your product. 276 | 277 | ## Follow us 278 | 279 | [![Twitter Follow](https://img.shields.io/twitter/follow/pgssoftware.svg?style=social&label=Follow)](https://twitter.com/pgssoftware) 280 | 281 | -------------------------------------------------------------------------------- /pgssoft-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PGSSoft/HttpClientMock/7d0a27d9dff9470f1937595b2a9a5b0258729cd5/pgssoft-logo.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.pgs-soft 6 | HttpClientMock 7 | 1.0.0 8 | jar 9 | 10 | ${project.groupId}:${project.artifactId} 11 | Library for mocking JDK 11 HttpClient. 12 | https://github.com/PGSSoft/HttpClientMock 13 | 14 | 15 | MIT License 16 | http://www.opensource.org/licenses/mit-license.php 17 | 18 | 19 | 20 | 21 | Paweł Adamski 22 | pawel.poczta@gmail.com 23 | 24 | 25 | Radosław Skupnik 26 | rskupnik@pgs-soft.com 27 | 28 | 29 | 30 | scm:git:git://github.com/PGSSoft/HttpClientMock.git 31 | scm:git:ssh://github.com:PGSSoft/HttpClientMock.git 32 | http://github.com/PGSSoft/HttpClientMock/tree/master 33 | 34 | 35 | 36 | 37 | UTF-8 38 | 11 39 | 11 40 | 41 | 5.3.2 42 | 1.3 43 | 2.19.1 44 | 1.1.0 45 | 3.1.10 46 | 47 | 48 | 49 | 50 | org.junit.jupiter 51 | junit-jupiter-api 52 | ${junit.version} 53 | test 54 | 55 | 56 | org.hamcrest 57 | hamcrest-all 58 | ${hamcrest.version} 59 | 60 | 61 | 62 | 63 | 64 | 65 | ossrh 66 | https://oss.sonatype.org/content/repositories/snapshots 67 | 68 | 69 | ossrh 70 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 71 | 72 | 73 | 74 | 75 | 76 | release 77 | 78 | 79 | 80 | org.sonatype.plugins 81 | nexus-staging-maven-plugin 82 | 1.6.7 83 | true 84 | 85 | ossrh 86 | https://oss.sonatype.org/ 87 | false 88 | 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-source-plugin 93 | 2.2.1 94 | 95 | 96 | attach-sources 97 | 98 | jar-no-fork 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-javadoc-plugin 106 | 3.1.0 107 | 108 | 109 | module-info.java 110 | 111 | 112 | 113 | 114 | attach-javadocs 115 | 116 | jar 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-gpg-plugin 124 | 1.5 125 | 126 | 127 | sign-artifacts 128 | verify 129 | 130 | sign 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.apache.maven.plugins 145 | maven-jar-plugin 146 | 3.1.1 147 | 148 | 149 | 150 | com.pgssoft.httpclient 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-compiler-plugin 158 | 3.8.0 159 | 160 | ${maven.compiler.source} 161 | ${maven.compiler.source} 162 | 163 | 164 | 165 | org.apache.maven.plugins 166 | maven-surefire-plugin 167 | ${surefire.version} 168 | 169 | 170 | org.junit.platform 171 | junit-platform-surefire-provider 172 | ${surefire.junit.version} 173 | 174 | 175 | org.junit.jupiter 176 | junit-jupiter-engine 177 | ${junit.version} 178 | 179 | 180 | 181 | 182 | com.github.spotbugs 183 | spotbugs-maven-plugin 184 | ${spotbug.version} 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/Action.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import java.io.IOException; 4 | 5 | public interface Action { 6 | void enrichResponse(MockedServerResponse.Builder responseBuilder) throws IOException; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/Condition.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import java.net.http.HttpRequest; 4 | 5 | public interface Condition { 6 | boolean matches(HttpRequest request); 7 | 8 | default String getDebugMessage() { 9 | return "Anonymous condition"; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/HttpClientMock.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.debug.Debugger; 4 | import com.pgssoft.httpclient.internal.HttpResponseProxy; 5 | import com.pgssoft.httpclient.internal.rule.Rule; 6 | import com.pgssoft.httpclient.internal.rule.RuleBuilder; 7 | 8 | import javax.net.ssl.SSLContext; 9 | import javax.net.ssl.SSLParameters; 10 | import java.io.IOException; 11 | import java.net.Authenticator; 12 | import java.net.CookieHandler; 13 | import java.net.ProxySelector; 14 | import java.net.http.HttpClient; 15 | import java.net.http.HttpHeaders; 16 | import java.net.http.HttpRequest; 17 | import java.net.http.HttpResponse; 18 | import java.nio.ByteBuffer; 19 | import java.time.Duration; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Objects; 23 | import java.util.Optional; 24 | import java.util.concurrent.CompletableFuture; 25 | import java.util.concurrent.ExecutionException; 26 | import java.util.concurrent.Executor; 27 | import java.util.concurrent.SubmissionPublisher; 28 | import java.util.stream.Collectors; 29 | 30 | import static com.pgssoft.httpclient.internal.HttpMethods.*; 31 | 32 | public final class HttpClientMock extends HttpClient { 33 | 34 | private final Debugger debugger; 35 | private final List rules = new ArrayList<>(); 36 | private final List rulesUnderConstruction = new ArrayList<>(); 37 | private final String host; 38 | private final List requests = new ArrayList<>(); 39 | 40 | private boolean debuggingOn; 41 | 42 | /** 43 | * Creates mock of HttpClient 44 | */ 45 | public HttpClientMock() { 46 | this(""); 47 | } 48 | 49 | /** 50 | * Creates mock of HttpClient with default host. All defined conditions without host will use default host 51 | * 52 | * @param host default host for later conditions 53 | */ 54 | public HttpClientMock(String host) { 55 | this.host = host; 56 | this.debugger = new Debugger(); 57 | } 58 | 59 | /** 60 | * Creates mock of HttpClient with default host. All defined conditions without host will use default host 61 | * 62 | * @param host default host for later conditions 63 | * @param debugger debugger used for testing 64 | */ 65 | HttpClientMock(String host, Debugger debugger) { 66 | this.host = host; 67 | this.debugger = debugger; 68 | } 69 | 70 | /** 71 | * Resets mock to initial state where there are no rules and no previous requests. 72 | */ 73 | public void reset() { 74 | this.rulesUnderConstruction.clear(); 75 | this.requests.clear(); 76 | } 77 | 78 | /** 79 | * Creates verification builder. 80 | * 81 | * @return request number verification builder 82 | */ 83 | public HttpClientVerify verify() { 84 | return new HttpClientVerify(host, requests); 85 | } 86 | 87 | /** 88 | * Starts defining new rule which requires HTTP POST method. 89 | * 90 | * @return HttpClientMockBuilder which allows to define new rule 91 | */ 92 | public HttpClientMockBuilder onPost() { 93 | return newRule(POST); 94 | } 95 | 96 | /** 97 | * Starts defining new rule which requires HTTP GET method. 98 | * 99 | * @return HttpClientMockBuilder which allows to define new rule 100 | */ 101 | public HttpClientMockBuilder onGet() { 102 | return newRule(GET); 103 | } 104 | 105 | /** 106 | * Starts defining new rule which requires HTTP DELETE method. 107 | * 108 | * @return HttpClientMockBuilder which allows to define new rule 109 | */ 110 | public HttpClientMockBuilder onDelete() { 111 | return newRule(DELETE); 112 | } 113 | 114 | /** 115 | * Starts defining new rule which requires HTTP HEAD method. 116 | * 117 | * @return HttpClientMockBuilder which allows to define new rule 118 | */ 119 | public HttpClientMockBuilder onHead() { 120 | return newRule(HEAD); 121 | } 122 | 123 | /** 124 | * Starts defining new rule which requires HTTP OPTION method. 125 | * 126 | * @return HttpClientMockBuilder which allows to define new rule 127 | */ 128 | public HttpClientMockBuilder onOptions() { 129 | return newRule(OPTIONS); 130 | } 131 | 132 | /** 133 | * Starts defining new rule which requires HTTP PUT method. 134 | * 135 | * @return HttpClientMockBuilder which allows to define new rule 136 | */ 137 | public HttpClientMockBuilder onPut() { 138 | return newRule(PUT); 139 | } 140 | 141 | /** 142 | * Starts defining new rule which requires HTTP PATCH method. 143 | * 144 | * @return HttpClientMockBuilder which allows to define new rule 145 | */ 146 | public HttpClientMockBuilder onPatch() { 147 | return newRule(PATCH); 148 | } 149 | 150 | /** 151 | * Starts defining new rule which requires HTTP GET method and url. If provided url starts with "/" request url must be equal to concatenation of default 152 | * host and url. Otherwise request url must equal to provided url. If provided url contains query parameters and/or reference they are parsed and added as a 153 | * separate conditions.

For example:
httpClientMock.onGet("http://localhost/login?user=Ben#edit");
is equal to
154 | * httpClientMock.onGet("http://localhost/login").withParameter("user","Ben").withReference("edit); 155 | * 156 | * @param url required url 157 | * @return HttpClientMockBuilder which allows to define new rule 158 | */ 159 | public HttpClientMockBuilder onGet(String url) { 160 | Objects.requireNonNull(url, "URL must be not null"); 161 | return newRule(GET, url); 162 | } 163 | 164 | /** 165 | * Starts defining new rule which requires HTTP POST method and url. URL works the same way as in {@link #onGet(String) onGet} 166 | * 167 | * @param url required url 168 | * @return HttpClientMockBuilder which allows to define new rule 169 | */ 170 | public HttpClientMockBuilder onPost(String url) { 171 | Objects.requireNonNull(url, "URL must be not null"); 172 | return newRule(POST, url); 173 | } 174 | 175 | /** 176 | * Starts defining new rule which requires HTTP PUT method and url. URL works the same way as in {@link #onGet(String) onGet} 177 | * 178 | * @param url required url 179 | * @return HttpClientMockBuilder which allows to define new rule 180 | */ 181 | public HttpClientMockBuilder onPut(String url) { 182 | Objects.requireNonNull(url, "URL must be not null"); 183 | return newRule(PUT, url); 184 | } 185 | 186 | /** 187 | * Starts defining new rule which requires HTTP DELETE method and url. URL works the same way as in {@link #onGet(String) onGet} 188 | * 189 | * @param url required url 190 | * @return HttpClientMockBuilder which allows to define new rule 191 | */ 192 | public HttpClientMockBuilder onDelete(String url) { 193 | Objects.requireNonNull(url, "URL must be not null"); 194 | return newRule(DELETE, url); 195 | } 196 | 197 | /** 198 | * Starts defining new rule which requires HTTP HEAD method and url. URL works the same way as in {@link #onGet(String) onGet} 199 | * 200 | * @param url required url 201 | * @return HttpClientMockBuilder which allows to define new rule 202 | */ 203 | public HttpClientMockBuilder onHead(String url) { 204 | Objects.requireNonNull(url, "URL must be not null"); 205 | return newRule(HEAD, url); 206 | } 207 | 208 | /** 209 | * Starts defining new rule which requires HTTP OPTIONS method and url. URL works the same way as in {@link #onGet(String) onGet} 210 | * 211 | * @param url required url 212 | * @return HttpClientMockBuilder which allows to define new rule 213 | */ 214 | public HttpClientMockBuilder onOptions(String url) { 215 | Objects.requireNonNull(url, "URL must be not null"); 216 | return newRule(OPTIONS, url); 217 | } 218 | 219 | /** 220 | * Starts defining new rule which requires HTTP PATCH method and url. URL works the same way as in {@link #onGet(String) onGet} 221 | * 222 | * @param url required url 223 | * @return HttpClientMockBuilder which allows to define new rule 224 | */ 225 | public HttpClientMockBuilder onPatch(String url) { 226 | Objects.requireNonNull(url, "URL must be not null"); 227 | return newRule(PATCH, url); 228 | } 229 | 230 | private HttpClientMockBuilder newRule(String method) { 231 | RuleBuilder r = new RuleBuilder(method); 232 | rulesUnderConstruction.add(r); 233 | return new HttpClientMockBuilder(r); 234 | } 235 | 236 | private HttpClientMockBuilder newRule(String method, String url) { 237 | RuleBuilder r = new RuleBuilder(method, host, url); 238 | rulesUnderConstruction.add(r); 239 | return new HttpClientMockBuilder(r); 240 | } 241 | 242 | @Override 243 | public Optional cookieHandler() { 244 | return Optional.empty(); 245 | } 246 | 247 | @Override 248 | public Optional connectTimeout() { 249 | return Optional.empty(); 250 | } 251 | 252 | @Override 253 | public Redirect followRedirects() { 254 | return null; 255 | } 256 | 257 | @Override 258 | public Optional proxy() { 259 | return Optional.empty(); 260 | } 261 | 262 | @Override 263 | public SSLContext sslContext() { 264 | return null; 265 | } 266 | 267 | @Override 268 | public SSLParameters sslParameters() { 269 | return null; 270 | } 271 | 272 | @Override 273 | public Optional authenticator() { 274 | return Optional.empty(); 275 | } 276 | 277 | @Override 278 | public Version version() { 279 | return null; 280 | } 281 | 282 | @Override 283 | public Optional executor() { 284 | return Optional.empty(); 285 | } 286 | 287 | @Override 288 | public HttpResponse send(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler) throws IOException { 289 | Objects.requireNonNull(request,"request must be not null"); 290 | Objects.requireNonNull(responseBodyHandler,"responseBodyHandler must be not null"); 291 | var rule = findNextRule(request); 292 | var serverResponse = rule.produceResponse(); 293 | var body = submitToBodyHandler(serverResponse, responseBodyHandler); 294 | var httpHeaders = HttpHeaders.of(serverResponse.headers(), (a, b) -> true); 295 | return new HttpResponseProxy<>(serverResponse.statusCode(), httpHeaders, body, request); 296 | } 297 | 298 | private Rule findNextRule(HttpRequest request) { 299 | synchronized (rulesUnderConstruction) { 300 | rules.addAll( 301 | rulesUnderConstruction.stream() 302 | .map(RuleBuilder::build) 303 | .collect(Collectors.toList()) 304 | ); 305 | rulesUnderConstruction.clear(); 306 | } 307 | 308 | requests.add(request); 309 | 310 | final Optional rule = rules.stream() 311 | .filter(r -> r.matches(request)) 312 | .reduce((a, b) -> b); 313 | 314 | if (debuggingOn || rule.isEmpty()) { 315 | debugger.debug(rules, request); 316 | } 317 | 318 | return rule.orElseThrow(() -> new NoMatchingRuleException(request)); 319 | } 320 | 321 | @Override 322 | public CompletableFuture> sendAsync(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler) { 323 | Objects.requireNonNull(request,"request must be not null"); 324 | Objects.requireNonNull(responseBodyHandler,"responseBodyHandler must be not null"); 325 | try { 326 | var rule = findNextRule(request); 327 | var serverResponse = rule.produceResponse(); 328 | var body = submitToBodyHandler(serverResponse, responseBodyHandler); 329 | var httpHeaders = HttpHeaders.of(serverResponse.headers(), (a, b) -> true); 330 | var response = new HttpResponseProxy<>(serverResponse.statusCode(), httpHeaders, body, request); 331 | return CompletableFuture.completedFuture(response); 332 | } catch (IOException e) { 333 | throw new IllegalStateException(e); 334 | } 335 | 336 | } 337 | 338 | @Override 339 | public CompletableFuture> sendAsync(HttpRequest request, HttpResponse.BodyHandler responseBodyHandler, HttpResponse.PushPromiseHandler pushPromiseHandler) { 340 | return sendAsync(request, responseBodyHandler); 341 | } 342 | 343 | public void debugOn() { 344 | debuggingOn = true; 345 | } 346 | 347 | public void debugOff() { 348 | debuggingOn = false; 349 | } 350 | 351 | private T submitToBodyHandler(MockedServerResponse serverResponse, HttpResponse.BodyHandler responseBodyHandler) { 352 | var bodyBytes = serverResponse.getBodyBytes(); 353 | var subscriber = responseBodyHandler.apply(produceResponseInfo(serverResponse)); 354 | var publisher = new SubmissionPublisher>(); 355 | publisher.subscribe(subscriber); 356 | if (bodyBytes.array().length != 0) { 357 | publisher.submit(List.of(bodyBytes)); 358 | } 359 | publisher.close(); 360 | try { 361 | return subscriber.getBody().toCompletableFuture().get(); 362 | } catch (InterruptedException | ExecutionException e) { 363 | throw new IllegalStateException("Error reading the mocked response body - did you forget to provide it? " + 364 | "If there should be no body, try using BodyHandlers.discarding() when making the request", e); 365 | } 366 | } 367 | 368 | private HttpResponse.ResponseInfo produceResponseInfo(MockedServerResponse response) { 369 | return new HttpResponse.ResponseInfo() { 370 | @Override 371 | public int statusCode() { 372 | return response.statusCode(); 373 | } 374 | 375 | @Override 376 | public HttpHeaders headers() { 377 | return HttpHeaders.of(response.headers(), (a, b) -> true); 378 | } 379 | 380 | @Override 381 | public Version version() { 382 | return Version.HTTP_1_1; 383 | } 384 | }; 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/HttpClientMockBuilder.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.condition.BodyCondition; 4 | import com.pgssoft.httpclient.internal.condition.HeaderCondition; 5 | import com.pgssoft.httpclient.internal.rule.RuleBuilder; 6 | import org.hamcrest.Matcher; 7 | import org.hamcrest.Matchers; 8 | 9 | import java.io.IOException; 10 | import java.nio.charset.Charset; 11 | import java.util.Objects; 12 | 13 | public final class HttpClientMockBuilder { 14 | 15 | private final RuleBuilder ruleBuilder; 16 | private final HttpClientResponseBuilder responseBuilder; 17 | 18 | HttpClientMockBuilder(RuleBuilder ruleBuilder) { 19 | this.ruleBuilder = ruleBuilder; 20 | this.responseBuilder = new HttpClientResponseBuilder(ruleBuilder); 21 | } 22 | 23 | /** 24 | * Adds header condition. Header must be equal to provided value. 25 | * 26 | * @param header header name 27 | * @param value expected value 28 | * @return condition builder 29 | */ 30 | public HttpClientMockBuilder withHeader(String header, String value) { 31 | Objects.requireNonNull(header, "header must be not null"); 32 | return withHeader(header, Matchers.equalTo(value)); 33 | } 34 | 35 | /** 36 | * Adds header condition. Header must be equal to provided value. 37 | * 38 | * @param header header name 39 | * @param matcher header value matcher 40 | * @return condition builder 41 | */ 42 | public HttpClientMockBuilder withHeader(String header, Matcher matcher) { 43 | Objects.requireNonNull(header, "header must be not null"); 44 | Objects.requireNonNull(matcher, "matcher must be not null"); 45 | ruleBuilder.addCondition(new HeaderCondition(header, matcher)); 46 | return this; 47 | } 48 | 49 | /** 50 | * Adds reference condition. Reference must be equal to provided value. 51 | * 52 | * @param reference expected reference 53 | * @return conditions builder 54 | */ 55 | public HttpClientMockBuilder withReference(String reference) { 56 | Objects.requireNonNull(reference, "reference must be not null"); 57 | return withReference(Matchers.equalTo(reference)); 58 | } 59 | 60 | /** 61 | * Adds reference condition. Reference must match. 62 | * 63 | * @param matcher reference matcher 64 | * @return conditions builder 65 | */ 66 | public HttpClientMockBuilder withReference(Matcher matcher) { 67 | Objects.requireNonNull(matcher, "matcher must be not null"); 68 | ruleBuilder.setReferenceCondition(matcher); 69 | return this; 70 | } 71 | 72 | /** 73 | * Adds parameter condition. Parameter must be equal to provided value. 74 | * 75 | * @param name parameter name 76 | * @param value expected parameter value 77 | * @return condition builder 78 | */ 79 | public HttpClientMockBuilder withParameter(String name, String value) { 80 | Objects.requireNonNull(name, "name must be not null"); 81 | Objects.requireNonNull(value, "value must be not null"); 82 | return withParameter(name, Matchers.equalTo(value)); 83 | } 84 | 85 | /** 86 | * Adds parameter condition. Parameter value must match. 87 | * 88 | * @param name parameter name 89 | * @param matcher parameter value matcher 90 | * @return condition builder 91 | */ 92 | public HttpClientMockBuilder withParameter(String name, Matcher matcher) { 93 | Objects.requireNonNull(name, "name must be not null"); 94 | Objects.requireNonNull(matcher, "matcher must be not null"); 95 | ruleBuilder.setParameterCondition(name, matcher); 96 | return this; 97 | } 98 | 99 | /** 100 | * Adds custom conditions. 101 | * 102 | * @param condition custom condition 103 | * @return condition builder 104 | */ 105 | public HttpClientMockBuilder with(Condition condition) { 106 | Objects.requireNonNull(condition, "condition must be not null"); 107 | ruleBuilder.addCondition(condition); 108 | return this; 109 | } 110 | 111 | /** 112 | * Adds body condition. Request body must match provided matcher. 113 | * 114 | * @param matcher custom condition 115 | * @return condition builder 116 | */ 117 | public HttpClientMockBuilder withBody(Matcher matcher) { 118 | Objects.requireNonNull(matcher, "matcher must be not null"); 119 | ruleBuilder.addCondition(new BodyCondition(matcher)); 120 | return this; 121 | } 122 | 123 | /** 124 | * Adds host condition. Request host must be equal to provided value. 125 | * 126 | * @param host expected host 127 | * @return condition builder 128 | */ 129 | public HttpClientMockBuilder withHost(String host) { 130 | Objects.requireNonNull(host, "host must be not null"); 131 | ruleBuilder.addHostCondition(host); 132 | return this; 133 | } 134 | 135 | /** 136 | * Adds path condition. Request path must be equal to provided value. 137 | * 138 | * @param path expected path 139 | * @return condition builder 140 | */ 141 | public HttpClientMockBuilder withPath(String path) { 142 | Objects.requireNonNull(path, "path must be not null"); 143 | return withPath(Matchers.equalTo(path)); 144 | } 145 | 146 | /** 147 | * Adds path condition. Request path must match. 148 | * 149 | * @param matcher path matcher 150 | * @return condition builder 151 | */ 152 | public HttpClientMockBuilder withPath(Matcher matcher) { 153 | Objects.requireNonNull(matcher, "matcher must be not null"); 154 | ruleBuilder.setPathCondition(matcher); 155 | return this; 156 | } 157 | 158 | /** 159 | * Adds custom action. 160 | * 161 | * @param action custom action 162 | * @return response builder 163 | */ 164 | public HttpClientResponseBuilder doAction(Action action) { 165 | Objects.requireNonNull(action, "action must be not null"); 166 | return responseBuilder.doAction(action); 167 | } 168 | 169 | /** 170 | * Adds action which returns provided response in UTF-8 and status 200. 171 | * 172 | * @param response response to return 173 | * @return response builder 174 | */ 175 | public HttpClientResponseBuilder doReturn(String response) { 176 | Objects.requireNonNull(response, "response must be not null"); 177 | return responseBuilder.doReturn(response); 178 | } 179 | 180 | /** 181 | * Adds action which returns provided response and status in UTF-8. 182 | * 183 | * @param statusCode status to return 184 | * @param response response to return 185 | * @return response builder 186 | */ 187 | public HttpClientResponseBuilder doReturn(int statusCode, String response) { 188 | Objects.requireNonNull(response, "response must be not null"); 189 | return responseBuilder.doReturn(statusCode, response); 190 | } 191 | 192 | /** 193 | * Adds action which returns provided response in provided charset and status 200. 194 | * 195 | * @param response response to return 196 | * @param charset charset to return 197 | * @return response builder 198 | */ 199 | public HttpClientResponseBuilder doReturn(String response, Charset charset) { 200 | Objects.requireNonNull(response, "response must be not null"); 201 | Objects.requireNonNull(charset, "charset must be not null"); 202 | return responseBuilder.doReturn(response, charset); 203 | } 204 | 205 | /** 206 | * Adds action which returns empty message and provided status. 207 | * 208 | * @param statusCode status to return 209 | * @return response builder 210 | */ 211 | public HttpClientResponseBuilder doReturnStatus(int statusCode) { 212 | return responseBuilder.doReturnStatus(statusCode); 213 | } 214 | 215 | /** 216 | * Adds action which throws provided exception. 217 | * 218 | * @param exception exception to be thrown 219 | * @return response builder 220 | */ 221 | public HttpClientResponseBuilder doThrowException(IOException exception) { 222 | Objects.requireNonNull(exception, "exception must be not null"); 223 | return responseBuilder.doThrowException(exception); 224 | } 225 | 226 | /** 227 | * Adds action which returns provided JSON in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/json". 228 | * 229 | * @param response JSON to return 230 | * @return response builder 231 | */ 232 | public HttpClientResponseBuilder doReturnJSON(String response) { 233 | Objects.requireNonNull(response, "response must be not null"); 234 | return responseBuilder.doReturnJSON(response); 235 | } 236 | 237 | /** 238 | * Adds action which returns provided JSON in provided encoding and status 200. Additionally it sets "Content-type" header to "application/json". 239 | * 240 | * @param response JSON to return 241 | * @return response builder 242 | */ 243 | public HttpClientResponseBuilder doReturnJSON(String response, Charset charset) { 244 | Objects.requireNonNull(response, "response must be not null"); 245 | Objects.requireNonNull(charset, "charset must be not null"); 246 | return responseBuilder.doReturnJSON(response, charset); 247 | } 248 | 249 | /** 250 | * Adds action which returns provided XML in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/xml". 251 | * 252 | * @param response JSON to return 253 | * @return response builder 254 | */ 255 | public HttpClientResponseBuilder doReturnXML(String response) { 256 | Objects.requireNonNull(response, "response must be not null"); 257 | return responseBuilder.doReturnXML(response); 258 | } 259 | 260 | /** 261 | * Adds action which returns provided XML in UTF-8 and status 200. Additionally it sets "Content-type" header to "application/xml". 262 | * 263 | * @param response JSON to return 264 | * @return response builder 265 | */ 266 | public HttpClientResponseBuilder doReturnXML(String response, Charset charset) { 267 | Objects.requireNonNull(response, "response must be not null"); 268 | Objects.requireNonNull(charset, "charset must be not null"); 269 | return responseBuilder.doReturnXML(response, charset); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/HttpClientResponseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.action.*; 4 | import com.pgssoft.httpclient.internal.rule.RuleBuilder; 5 | 6 | import java.io.IOException; 7 | import java.nio.charset.Charset; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | public final class HttpClientResponseBuilder { 11 | 12 | private static final String APPLICATION_JSON = "application/json"; 13 | private static final String APPLICATION_XML = "application/xml"; 14 | 15 | private final RuleBuilder ruleBuilder; 16 | 17 | HttpClientResponseBuilder(RuleBuilder ruleBuilder) { 18 | this.ruleBuilder = ruleBuilder; 19 | } 20 | 21 | public HttpClientResponseBuilder withHeader(String name, String value) { 22 | return doAction(new SetHeaderAction(name, value)); 23 | } 24 | 25 | public HttpClientResponseBuilder withStatus(int statusCode) { 26 | return doAction(new SetStatusAction(statusCode)); 27 | } 28 | 29 | public HttpClientResponseBuilder doAction(Action action) { 30 | ruleBuilder.addAction(action); 31 | return this; 32 | } 33 | 34 | public HttpClientResponseBuilder doReturn(String response) { 35 | return doReturn(response, StandardCharsets.UTF_8); 36 | } 37 | 38 | public HttpClientResponseBuilder doReturn(int statusCode, String response) { 39 | return doReturn(statusCode, response, StandardCharsets.UTF_8); 40 | } 41 | 42 | public HttpClientResponseBuilder doReturn(String response, Charset charset) { 43 | return doReturn(200, response, charset); 44 | } 45 | 46 | public HttpClientResponseBuilder doReturn(int statusCode, String response, Charset charset) { 47 | ruleBuilder.addActionBundle(new SetBodyStringAction(response, charset)); 48 | ruleBuilder.addAction(new SetStatusAction(statusCode)); 49 | return new HttpClientResponseBuilder(ruleBuilder); 50 | } 51 | 52 | public HttpClientResponseBuilder doReturnStatus(int statusCode) { 53 | ruleBuilder.addActionBundle(new SetStatusAction(statusCode)); 54 | return new HttpClientResponseBuilder(ruleBuilder); 55 | } 56 | 57 | public HttpClientResponseBuilder doThrowException(IOException exception) { 58 | ruleBuilder.addActionBundle(new ThrowExceptionAction(exception)); 59 | return new HttpClientResponseBuilder(ruleBuilder); 60 | } 61 | 62 | public HttpClientResponseBuilder doReturnJSON(String response) { 63 | return doReturnJSON(response, StandardCharsets.UTF_8); 64 | } 65 | 66 | public HttpClientResponseBuilder doReturnJSON(String response, Charset charset) { 67 | return doReturn(response, charset).withHeader("Content-type", buildContentTypeHeader(APPLICATION_JSON,charset)); 68 | } 69 | 70 | 71 | public HttpClientResponseBuilder doReturnXML(String response) { 72 | return doReturnXML(response, StandardCharsets.UTF_8); 73 | } 74 | 75 | public HttpClientResponseBuilder doReturnXML(String response, Charset charset) { 76 | return doReturn(response, charset).withHeader("Content-type", buildContentTypeHeader(APPLICATION_XML,charset)); 77 | } 78 | 79 | private String buildContentTypeHeader(String type, Charset charset) { 80 | return String.format("%s; charset=%s",type,charset.name()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/HttpClientVerify.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.rule.RuleBuilder; 4 | 5 | import java.net.http.HttpRequest; 6 | import java.util.List; 7 | import java.util.Objects; 8 | 9 | import static com.pgssoft.httpclient.internal.HttpMethods.*; 10 | 11 | public final class HttpClientVerify { 12 | 13 | private final String defaultHost; 14 | private final List requests; 15 | 16 | HttpClientVerify(String defaultHost, List requests) { 17 | this.requests = requests; 18 | this.defaultHost = defaultHost; 19 | } 20 | 21 | private HttpClientVerifyBuilder newRule(String method) { 22 | RuleBuilder r = new RuleBuilder(method); 23 | return new HttpClientVerifyBuilder(r, requests); 24 | } 25 | 26 | private HttpClientVerifyBuilder newRule(String method, String url) { 27 | RuleBuilder r = new RuleBuilder(method, defaultHost, url); 28 | return new HttpClientVerifyBuilder(r, requests); 29 | } 30 | 31 | public HttpClientVerifyBuilder post(String url) { 32 | Objects.requireNonNull(url, "URL must be not null"); 33 | return newRule(POST, url); 34 | } 35 | 36 | public HttpClientVerifyBuilder get(String url) { 37 | Objects.requireNonNull(url, "URL must be not null"); 38 | return newRule(GET, url); 39 | } 40 | 41 | public HttpClientVerifyBuilder put(String url) { 42 | Objects.requireNonNull(url, "URL must be not null"); 43 | return newRule(PUT, url); 44 | } 45 | 46 | public HttpClientVerifyBuilder delete(String url) { 47 | Objects.requireNonNull(url, "URL must be not null"); 48 | return newRule(DELETE, url); 49 | } 50 | 51 | public HttpClientVerifyBuilder head(String url) { 52 | Objects.requireNonNull(url, "URL must be not null"); 53 | return newRule(HEAD, url); 54 | } 55 | 56 | public HttpClientVerifyBuilder options(String url) { 57 | Objects.requireNonNull(url, "URL must be not null"); 58 | return newRule(OPTIONS, url); 59 | } 60 | 61 | public HttpClientVerifyBuilder patch(String url) { 62 | Objects.requireNonNull(url, "URL must be not null"); 63 | return newRule(PATCH, url); 64 | } 65 | 66 | public HttpClientVerifyBuilder post() { 67 | return newRule(POST); 68 | } 69 | 70 | public HttpClientVerifyBuilder get() { 71 | return newRule(GET); 72 | } 73 | 74 | public HttpClientVerifyBuilder put() { 75 | return newRule(PUT); 76 | } 77 | 78 | public HttpClientVerifyBuilder delete() { 79 | return newRule(DELETE); 80 | } 81 | 82 | public HttpClientVerifyBuilder head() { 83 | return newRule(HEAD); 84 | } 85 | 86 | public HttpClientVerifyBuilder options() { 87 | return newRule(OPTIONS); 88 | } 89 | 90 | public HttpClientVerifyBuilder patch() { 91 | return newRule(PATCH); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/HttpClientVerifyBuilder.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.condition.BodyCondition; 4 | import com.pgssoft.httpclient.internal.condition.HeaderCondition; 5 | import com.pgssoft.httpclient.internal.rule.Rule; 6 | import com.pgssoft.httpclient.internal.rule.RuleBuilder; 7 | import org.hamcrest.Matcher; 8 | 9 | import java.net.http.HttpRequest; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | import static org.hamcrest.Matchers.equalTo; 14 | 15 | public final class HttpClientVerifyBuilder { 16 | 17 | private final RuleBuilder ruleBuilder; 18 | private final List requests; 19 | 20 | HttpClientVerifyBuilder(RuleBuilder ruleBuilder, List requests) { 21 | this.requests = requests; 22 | this.ruleBuilder = ruleBuilder; 23 | } 24 | 25 | /** 26 | * Adds header condition. Header must be equal to provided value. 27 | * 28 | * @param header header name 29 | * @param value expected value 30 | * @return verification builder 31 | */ 32 | public HttpClientVerifyBuilder withHeader(String header, String value) { 33 | Objects.requireNonNull(header, "header must be not null"); 34 | return withHeader(header, equalTo(value)); 35 | } 36 | 37 | /** 38 | * Adds header condition. Header must be equal to provided value. 39 | * 40 | * @param header header name 41 | * @param matcher header value matcher 42 | * @return verification builder 43 | */ 44 | public HttpClientVerifyBuilder withHeader(String header, Matcher matcher) { 45 | Objects.requireNonNull(header, "header must be not null"); 46 | Objects.requireNonNull(matcher, "matcher must be not null"); 47 | ruleBuilder.addCondition(new HeaderCondition(header, matcher)); 48 | return this; 49 | } 50 | 51 | /** 52 | * Adds reference condition. Reference must be equal to provided value. 53 | * 54 | * @param reference expected reference 55 | * @return conditions builder 56 | */ 57 | public HttpClientVerifyBuilder withReference(String reference) { 58 | Objects.requireNonNull(reference, "reference must be not null"); 59 | return withReference(equalTo(reference)); 60 | } 61 | 62 | /** 63 | * Adds reference condition. Reference must match. 64 | * 65 | * @param matcher reference matcher 66 | * @return conditions builder 67 | */ 68 | public HttpClientVerifyBuilder withReference(Matcher matcher) { 69 | Objects.requireNonNull(matcher, "matcher must be not null"); 70 | ruleBuilder.setReferenceCondition(matcher); 71 | return this; 72 | } 73 | 74 | /** 75 | * Adds parameter condition. Parameter must be equal to provided value. 76 | * 77 | * @param name parameter name 78 | * @param value expected parameter value 79 | * @return verification builder 80 | */ 81 | public HttpClientVerifyBuilder withParameter(String name, String value) { 82 | Objects.requireNonNull(name, "name must be not null"); 83 | Objects.requireNonNull(value, "value must be not null"); 84 | return withParameter(name, equalTo(value)); 85 | } 86 | 87 | /** 88 | * Adds parameter condition. Parameter value must match. 89 | * 90 | * @param name parameter name 91 | * @param matcher parameter value matcher 92 | * @return verification builder 93 | */ 94 | public HttpClientVerifyBuilder withParameter(String name, Matcher matcher) { 95 | Objects.requireNonNull(name, "name must be not null"); 96 | Objects.requireNonNull(matcher, "matcher must be not null"); 97 | ruleBuilder.setParameterCondition(name, matcher); 98 | return this; 99 | } 100 | 101 | /** 102 | * Adds custom conditions. 103 | * 104 | * @param condition custom condition 105 | * @return verification builder 106 | */ 107 | public HttpClientVerifyBuilder with(Condition condition) { 108 | Objects.requireNonNull(condition, "condition must be not null"); 109 | ruleBuilder.addCondition(condition); 110 | return this; 111 | } 112 | 113 | /** 114 | * Adds body condition. Request body must match provided matcher. 115 | * 116 | * @param matcher custom condition 117 | * @return verification builder 118 | */ 119 | public HttpClientVerifyBuilder withBody(Matcher matcher) { 120 | Objects.requireNonNull(matcher, "matcher must be not null"); 121 | ruleBuilder.addCondition(new BodyCondition(matcher)); 122 | return this; 123 | } 124 | 125 | /** 126 | * Adds host condition. Request host must be equal to provided value. 127 | * 128 | * @param host expected host 129 | * @return verification builder 130 | */ 131 | public HttpClientVerifyBuilder withHost(String host) { 132 | Objects.requireNonNull(host, "host must be not null"); 133 | ruleBuilder.addHostCondition(host); 134 | return this; 135 | } 136 | 137 | /** 138 | * Adds path condition. Request path must be equal to provided value. 139 | * 140 | * @param path expected path 141 | * @return verification builder 142 | */ 143 | public HttpClientVerifyBuilder withPath(String path) { 144 | Objects.requireNonNull(path, "path must be not null"); 145 | return withPath(equalTo(path)); 146 | } 147 | 148 | /** 149 | * Adds path condition. Request path must match. 150 | * 151 | * @param matcher path matcher 152 | * @return verification builder 153 | */ 154 | public HttpClientVerifyBuilder withPath(Matcher matcher) { 155 | Objects.requireNonNull(matcher, "matcher must be not null"); 156 | ruleBuilder.setPathCondition(matcher); 157 | return this; 158 | } 159 | 160 | /** 161 | * Verifies if there were no request matching defined conditions. 162 | */ 163 | public void notCalled() { 164 | called(0); 165 | } 166 | 167 | /** 168 | * Verifies if there was exactly one request matching defined conditions. 169 | */ 170 | public void called() { 171 | called(1); 172 | } 173 | 174 | /** 175 | * Verifies number of request matching defined conditions. 176 | * 177 | * @param numberOfCalls expected number of calls 178 | */ 179 | public void called(int numberOfCalls) { 180 | called(equalTo(numberOfCalls)); 181 | } 182 | 183 | /** 184 | * Verifies number of request matching defined conditions. 185 | * 186 | * @param numberOfCalls expected number of calls 187 | */ 188 | public void called(Matcher numberOfCalls) { 189 | Rule rule = ruleBuilder.build(); 190 | int matchingCalls = (int)requests.stream() 191 | .filter(rule::matches) 192 | .count(); 193 | if (!numberOfCalls.matches(matchingCalls)) { 194 | throw new IllegalStateException(String.format("Expected %s calls, but found %s.", numberOfCalls, matchingCalls)); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/MockedServerResponse.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.*; 5 | 6 | public class MockedServerResponse { 7 | 8 | private final int statusCode; 9 | private final Map> headers; 10 | private final ByteBuffer bodyBytes; 11 | 12 | private MockedServerResponse(int statusCode, Map> headers, ByteBuffer bodyBytes) { 13 | this.statusCode = statusCode; 14 | this.headers = headers; 15 | this.bodyBytes = bodyBytes; 16 | } 17 | 18 | public int statusCode() { 19 | return statusCode; 20 | } 21 | 22 | public Map> headers() { 23 | return headers; 24 | } 25 | 26 | public ByteBuffer getBodyBytes() { 27 | return bodyBytes; 28 | } 29 | 30 | 31 | public final static class Builder { 32 | 33 | private int statusCode; 34 | private final Map> headers = new HashMap<>(); 35 | private ByteBuffer bodyBytes = ByteBuffer.wrap(new byte[]{}); 36 | 37 | public void setStatusCode(int statusCode) { 38 | this.statusCode = statusCode; 39 | } 40 | 41 | public void addHeader(String key, String value) { 42 | headers.computeIfAbsent(key, k -> new ArrayList<>()).add(value); 43 | } 44 | 45 | public void setBodyBytes(ByteBuffer bodyBytes) { 46 | this.bodyBytes = bodyBytes; 47 | } 48 | 49 | public MockedServerResponse build() { 50 | return new MockedServerResponse(statusCode, headers, bodyBytes); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/NoMatchingRuleException.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import java.net.http.HttpRequest; 4 | 5 | class NoMatchingRuleException extends IllegalStateException { 6 | NoMatchingRuleException(HttpRequest request) { 7 | super("No rule found for request: [" + request.method() + ": " + request.uri() + "]"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/HttpMethods.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | public class HttpMethods { 4 | public static final String POST = "POST"; 5 | public static final String GET = "GET"; 6 | public static final String DELETE = "DELETE"; 7 | public static final String HEAD = "HEAD"; 8 | public static final String OPTIONS = "OPTIONS"; 9 | public static final String PUT = "PUT"; 10 | public static final String PATCH = "PATCH"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/HttpResponseProxy.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import javax.net.ssl.SSLSession; 4 | import java.net.URI; 5 | import java.net.http.HttpClient; 6 | import java.net.http.HttpHeaders; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | import java.util.*; 10 | 11 | public class HttpResponseProxy implements HttpResponse { 12 | 13 | private final int statusCode; 14 | private final HttpHeaders headers; 15 | private final HttpRequest request; 16 | private final T body; 17 | 18 | public HttpResponseProxy(int statusCode, HttpHeaders headers, T body, HttpRequest request) { 19 | this.request = request; 20 | this.statusCode = statusCode; 21 | this.headers = headers; 22 | this.body = body; 23 | } 24 | 25 | @Override 26 | public int statusCode() { 27 | return statusCode; 28 | } 29 | 30 | @Override 31 | public HttpRequest request() { 32 | return request; 33 | } 34 | 35 | @Override 36 | public Optional> previousResponse() { 37 | return Optional.empty(); 38 | } 39 | 40 | @Override 41 | public HttpHeaders headers() { 42 | return headers; 43 | } 44 | 45 | @Override 46 | public T body() { 47 | return body; 48 | } 49 | 50 | @Override 51 | public Optional sslSession() { 52 | return Optional.empty(); 53 | } 54 | 55 | @Override 56 | public URI uri() { 57 | return request.uri(); 58 | } 59 | 60 | @Override 61 | public HttpClient.Version version() { 62 | return request.version().orElse(HttpClient.Version.HTTP_1_1); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/ParameterValue.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import java.util.List; 4 | 5 | class ParameterValue { 6 | 7 | final String name; 8 | final List values; 9 | 10 | public ParameterValue(String name, List values) { 11 | this.name = name; 12 | this.values = values; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public List getValues() { 20 | return values; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/PeekSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.concurrent.Flow; 5 | 6 | public class PeekSubscriber implements Flow.Subscriber { 7 | 8 | private Flow.Subscription subscription; 9 | private ByteBuffer content; 10 | 11 | @Override 12 | public void onSubscribe(Flow.Subscription subscription) { 13 | this.subscription = subscription; 14 | this.subscription.request(1); 15 | } 16 | 17 | @Override 18 | public void onNext(ByteBuffer item) { 19 | content = item; 20 | this.subscription.request(1); 21 | } 22 | 23 | @Override 24 | public void onError(Throwable throwable) { 25 | throwable.printStackTrace(); 26 | } 27 | 28 | @Override 29 | public void onComplete() { 30 | 31 | } 32 | 33 | public ByteBuffer content() { 34 | return content; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/UrlConditions.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import com.pgssoft.httpclient.internal.debug.Debugger; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.Matchers; 6 | import org.hamcrest.StringDescription; 7 | 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.net.http.HttpRequest; 11 | 12 | import static org.hamcrest.Matchers.equalTo; 13 | 14 | public class UrlConditions { 15 | 16 | private static final int EMPTY_PORT = -1; 17 | private UrlParamsMatcher parameterConditions = new UrlParamsMatcher(); 18 | private Matcher referenceCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue()); 19 | private Matcher hostCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue()); 20 | private Matcher pathCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue()); 21 | private Matcher portCondition = Matchers.any(Integer.class); 22 | private Matcher schemaCondition = Matchers.anyOf(Matchers.any(String.class), Matchers.nullValue()); 23 | 24 | public static UrlConditions parse(String urlText) { 25 | try { 26 | UrlConditions conditions = new UrlConditions(); 27 | URI uri = new URI(urlText); 28 | UrlParams params = UrlParams.parse(uri.getQuery()); 29 | conditions.setSchemaCondition(getStringMatcher(uri.getScheme())); 30 | conditions.setHostCondition(getStringMatcher(uri.getHost())); 31 | conditions.setPortCondition(equalTo(uri.getPort())); 32 | conditions.setPathCondition(getStringMatcher(uri.getPath())); 33 | conditions.setReferenceCondition(getStringMatcher(uri.getFragment())); 34 | conditions.setParameterConditions(new UrlParamsMatcher(params)); 35 | return conditions; 36 | } catch (URISyntaxException e) { 37 | throw new IllegalArgumentException(e); 38 | } 39 | } 40 | 41 | 42 | private static Matcher getStringMatcher(String val) { 43 | if (val == null || val.isEmpty()) { 44 | return Matchers.isEmptyOrNullString(); 45 | } else { 46 | return Matchers.equalTo(val); 47 | } 48 | } 49 | 50 | public boolean matches(URI uri) { 51 | return hostCondition.matches(uri.getHost()) 52 | && pathCondition.matches(uri.getPath()) 53 | && portCondition.matches(uri.getPort()) 54 | && referenceCondition.matches(uri.getFragment()) 55 | && schemaCondition.matches(uri.getScheme()) 56 | && parameterConditions.matches(uri.getQuery()); 57 | 58 | } 59 | 60 | public void debug(HttpRequest request, Debugger debugger) { 61 | URI uri = request.uri(); 62 | debugger.message(schemaCondition.matches(uri.getScheme()), "schema is " + describeMatcher(schemaCondition)); 63 | debugger.message(hostCondition.matches(uri.getHost()), "host is " + describeMatcher(hostCondition)); 64 | debugger.message(portCondition.matches(uri.getPort()), "port is " + portDebugDescription()); 65 | debugger.message(pathCondition.matches(uri.getPath()), "path is " + describeMatcher(pathCondition)); 66 | debugger.message(parameterConditions.matches(uri.getQuery()), "all URI parameters have matching value"); 67 | debugger.message(referenceCondition.matches(uri.getFragment()), "URI reference has matching value"); 68 | } 69 | 70 | private String portDebugDescription() { 71 | if (portCondition.matches(EMPTY_PORT)) { 72 | return "empty"; 73 | } else { 74 | return describeMatcher(portCondition); 75 | } 76 | } 77 | 78 | private String describeMatcher(Matcher matcher) { 79 | return StringDescription.toString(matcher); 80 | } 81 | 82 | 83 | public UrlParamsMatcher getParameterConditions() { 84 | return parameterConditions; 85 | } 86 | 87 | public void setParameterConditions(UrlParamsMatcher parameterConditions) { 88 | this.parameterConditions = parameterConditions; 89 | } 90 | 91 | public Matcher getReferenceCondition() { 92 | return referenceCondition; 93 | } 94 | 95 | public void setReferenceCondition(Matcher referenceCondition) { 96 | this.referenceCondition = referenceCondition; 97 | } 98 | 99 | public Matcher getHostCondition() { 100 | return hostCondition; 101 | } 102 | 103 | public void setHostCondition(Matcher hostCondition) { 104 | this.hostCondition = hostCondition; 105 | } 106 | 107 | public Matcher getPathCondition() { 108 | return pathCondition; 109 | } 110 | 111 | public void setPathCondition(Matcher pathCondition) { 112 | this.pathCondition = pathCondition; 113 | } 114 | 115 | public Matcher getPortCondition() { 116 | return portCondition; 117 | } 118 | 119 | public void setPortCondition(Matcher portCondition) { 120 | this.portCondition = portCondition; 121 | } 122 | 123 | public Matcher getSchemaCondition() { 124 | return schemaCondition; 125 | } 126 | 127 | public void setSchemaCondition(Matcher schemaCondition) { 128 | this.schemaCondition = schemaCondition; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/UrlParams.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import java.util.*; 4 | import java.util.stream.Collectors; 5 | 6 | import static java.util.stream.Collectors.mapping; 7 | import static java.util.stream.Collectors.toList; 8 | 9 | class UrlParams { 10 | 11 | private final ArrayList params = new ArrayList<>(); 12 | 13 | boolean contains(String name) { 14 | return params.stream().anyMatch(p -> p.getName().equals(name)); 15 | } 16 | 17 | public List getParams() { 18 | return params; 19 | } 20 | 21 | 22 | static UrlParams parse(String query) { 23 | if (query == null) { 24 | return new UrlParams(); 25 | } else { 26 | UrlParams urlParams = new UrlParams(); 27 | splitQuery(query).forEach((k, v) -> urlParams.params.add(new ParameterValue(k, v))); 28 | return urlParams; 29 | } 30 | } 31 | 32 | 33 | private static Map> splitQuery(String query) { 34 | if (query == null || query.length() <= 0) { 35 | return Collections.emptyMap(); 36 | } 37 | 38 | return Arrays.stream(query.split("&")) 39 | .map(UrlParams::splitQueryParameter) 40 | .collect(Collectors.groupingBy(AbstractMap.SimpleImmutableEntry::getKey, LinkedHashMap::new, mapping(Map.Entry::getValue, toList()))); 41 | } 42 | 43 | private static AbstractMap.SimpleImmutableEntry splitQueryParameter(String it) { 44 | final int idx = it.indexOf("="); 45 | final String key = idx > 0 ? it.substring(0, idx) : it; 46 | final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null; 47 | return new AbstractMap.SimpleImmutableEntry<>(key, value); 48 | } 49 | 50 | public Set getNames() { 51 | return params.stream().map(ParameterValue::getName).collect(Collectors.toSet()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/UrlParamsMatcher.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import org.hamcrest.Matcher; 4 | import org.hamcrest.Matchers; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class UrlParamsMatcher { 11 | 12 | private Map>> matchers; 13 | 14 | public UrlParamsMatcher(UrlParams params) { 15 | matchers = new HashMap<>(); 16 | for (ParameterValue param : params.getParams()) { 17 | String[] values = param.getValues().toArray(new String[]{}); 18 | matchers.put(param.getName(), Matchers.containsInAnyOrder(values)); 19 | } 20 | } 21 | 22 | public UrlParamsMatcher() { 23 | } 24 | 25 | 26 | private boolean setsOfParametersAreEqual(String query) { 27 | Set expectedParams = matchers.keySet(); 28 | Set actualParams = UrlParams.parse(query).getNames(); 29 | return expectedParams.equals(actualParams); 30 | } 31 | 32 | private boolean allParamsHaveMatchingValue(String query) { 33 | UrlParams params = UrlParams.parse(query); 34 | return params.getParams().stream() 35 | .allMatch(param -> matchers.get(param.getName()).matches(param.getValues())); 36 | } 37 | 38 | 39 | public boolean matches(String query) { 40 | return noMatchersWereDefined() || 41 | (setsOfParametersAreEqual(query) && allParamsHaveMatchingValue(query)); 42 | } 43 | 44 | private boolean noMatchersWereDefined() { 45 | return matchers == null; 46 | } 47 | 48 | public void addParam(String name, Matcher> matcher) { 49 | if (noMatchersWereDefined()) { 50 | matchers = new HashMap<>(); 51 | } 52 | matchers.put(name, matcher); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/action/ActionBundle.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.action; 2 | 3 | import com.pgssoft.httpclient.Action; 4 | 5 | import java.util.LinkedList; 6 | 7 | public final class ActionBundle extends LinkedList { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/action/SetBodyStringAction.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.action; 2 | 3 | import com.pgssoft.httpclient.Action; 4 | import com.pgssoft.httpclient.MockedServerResponse; 5 | 6 | import java.nio.charset.Charset; 7 | 8 | public final class SetBodyStringAction implements Action { 9 | 10 | private final String content; 11 | private final Charset charset; 12 | 13 | public SetBodyStringAction(String content, Charset charset) { 14 | this.content = content; 15 | this.charset = charset; 16 | } 17 | 18 | @Override 19 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) { 20 | responseBuilder.setBodyBytes(charset.encode(content)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/action/SetHeaderAction.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.action; 2 | 3 | import com.pgssoft.httpclient.Action; 4 | import com.pgssoft.httpclient.MockedServerResponse; 5 | 6 | 7 | public final class SetHeaderAction implements Action { 8 | 9 | private final String key, value; 10 | 11 | public SetHeaderAction(String key, String value) { 12 | this.key = key; 13 | this.value = value; 14 | } 15 | 16 | @Override 17 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) { 18 | responseBuilder.addHeader(key, value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/action/SetStatusAction.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.action; 2 | 3 | import com.pgssoft.httpclient.Action; 4 | import com.pgssoft.httpclient.MockedServerResponse; 5 | 6 | public final class SetStatusAction implements Action { 7 | 8 | private final int status; 9 | 10 | public SetStatusAction(int status) { 11 | this.status = status; 12 | } 13 | 14 | @Override 15 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) { 16 | responseBuilder.setStatusCode(status); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/action/ThrowExceptionAction.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.action; 2 | 3 | import com.pgssoft.httpclient.Action; 4 | import com.pgssoft.httpclient.MockedServerResponse; 5 | 6 | import java.io.IOException; 7 | 8 | public final class ThrowExceptionAction implements Action { 9 | 10 | private final IOException exception; 11 | 12 | public ThrowExceptionAction(IOException exception) { 13 | this.exception = exception; 14 | } 15 | 16 | @Override 17 | public void enrichResponse(MockedServerResponse.Builder responseBuilder) throws IOException { 18 | throw exception; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/condition/BodyCondition.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.condition; 2 | 3 | import com.pgssoft.httpclient.Condition; 4 | import com.pgssoft.httpclient.internal.PeekSubscriber; 5 | import org.hamcrest.Matcher; 6 | 7 | import java.net.http.HttpRequest; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.Optional; 10 | 11 | public final class BodyCondition implements Condition { 12 | 13 | private final Matcher matcher; 14 | 15 | public BodyCondition(Matcher matcher) { 16 | this.matcher = matcher; 17 | } 18 | 19 | @Override 20 | public boolean matches(HttpRequest request) { 21 | final Optional bodyPublisher = request.bodyPublisher(); 22 | if (bodyPublisher.isEmpty()) { 23 | return matcher.matches(null); 24 | } 25 | 26 | final var subscriber = new PeekSubscriber(); 27 | request.bodyPublisher().orElseThrow().subscribe(subscriber); 28 | final var content = subscriber.content(); 29 | return content != null && matcher.matches(new String(content.array(), StandardCharsets.UTF_8)); 30 | } 31 | 32 | @Override 33 | public String getDebugMessage() { 34 | return "body matches"; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/condition/HeaderCondition.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.condition; 2 | 3 | import com.pgssoft.httpclient.Condition; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.StringDescription; 6 | 7 | import java.net.http.HttpRequest; 8 | import java.util.Objects; 9 | 10 | public final class HeaderCondition implements Condition { 11 | 12 | private final String header; 13 | private final Matcher expectedValue; 14 | 15 | public HeaderCondition(String header, Matcher expectedValue) { 16 | this.header = header; 17 | this.expectedValue = expectedValue; 18 | } 19 | 20 | @Override 21 | public boolean matches(HttpRequest request) { 22 | return request.headers().allValues(header) 23 | .stream() 24 | .filter(Objects::nonNull) 25 | .anyMatch(expectedValue::matches); 26 | } 27 | 28 | @Override 29 | public String getDebugMessage() { 30 | return "header " + header + " is " + StringDescription.toString(expectedValue); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/condition/MethodCondition.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.condition; 2 | 3 | import com.pgssoft.httpclient.Condition; 4 | 5 | import java.net.http.HttpRequest; 6 | 7 | public final class MethodCondition implements Condition { 8 | 9 | private final String method; 10 | 11 | public MethodCondition(String method) { 12 | this.method = method; 13 | } 14 | 15 | @Override 16 | public boolean matches(HttpRequest request) { 17 | return request.method().equals(method); 18 | } 19 | 20 | @Override 21 | public String getDebugMessage() { 22 | return "HTTP method is " + method; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/debug/Debugger.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.debug; 2 | 3 | import com.pgssoft.httpclient.internal.rule.Rule; 4 | 5 | import java.net.http.HttpRequest; 6 | import java.util.List; 7 | 8 | public class Debugger { 9 | 10 | public void debug(List rules, HttpRequest request) { 11 | logRequest(request); 12 | logRules(rules, request); 13 | } 14 | 15 | private void logRules(List rules, HttpRequest request) { 16 | if (rules.size() == 0) { 17 | System.out.println("No rules were defined."); 18 | } 19 | for (int i = 0; i < rules.size(); i++) { 20 | System.out.println("Rule " + (i + 1) + ":"); 21 | System.out.println("\tMATCHES\t\tEXPECTED"); 22 | rules.get(i).debug(request, this); 23 | } 24 | System.out.println(); 25 | System.out.println("----------------"); 26 | 27 | } 28 | 29 | private void logRequest(HttpRequest request) { 30 | System.out.println("Request: " + request.method() + " " + request.uri()); 31 | } 32 | 33 | public void message(boolean matches, String expected) { 34 | String debugMessage = String.format("\t%s\t\t%s", matches, expected); 35 | System.out.println(debugMessage); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/rule/Rule.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.rule; 2 | 3 | import com.pgssoft.httpclient.MockedServerResponse; 4 | import com.pgssoft.httpclient.internal.UrlConditions; 5 | import com.pgssoft.httpclient.Action; 6 | import com.pgssoft.httpclient.internal.action.ActionBundle; 7 | import com.pgssoft.httpclient.Condition; 8 | import com.pgssoft.httpclient.internal.debug.Debugger; 9 | 10 | import java.io.IOException; 11 | import java.net.http.HttpRequest; 12 | import java.util.List; 13 | import java.util.Queue; 14 | 15 | public final class Rule { 16 | 17 | private final UrlConditions urlConditions; 18 | private final List conditions; 19 | private final Queue actionBundles; 20 | 21 | public Rule(UrlConditions urlConditions, List conditions, Queue actionBundles) { 22 | this.urlConditions = urlConditions; 23 | this.conditions = conditions; 24 | this.actionBundles = actionBundles; 25 | } 26 | 27 | public boolean matches(HttpRequest request) { 28 | return urlConditions.matches(request.uri()) && 29 | conditions.stream().allMatch(c -> c.matches(request)); 30 | } 31 | 32 | public MockedServerResponse produceResponse() throws IOException { 33 | final var responseBuilder = new MockedServerResponse.Builder(); 34 | 35 | final var actionBundle = actionBundles.size() > 1 ? actionBundles.poll() : actionBundles.peek(); 36 | for (Action a : actionBundle) { 37 | a.enrichResponse(responseBuilder); 38 | } 39 | 40 | return responseBuilder.build(); 41 | } 42 | 43 | public void debug(HttpRequest request, Debugger debugger) { 44 | for (Condition condition : conditions) { 45 | debugCondition(condition, request, debugger); 46 | } 47 | urlConditions.debug(request, debugger); 48 | } 49 | 50 | private void debugCondition(Condition condition, HttpRequest request, Debugger debugger) { 51 | debugger.message(condition.matches(request), condition.getDebugMessage()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/pgssoft/httpclient/internal/rule/RuleBuilder.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal.rule; 2 | 3 | import com.pgssoft.httpclient.internal.UrlConditions; 4 | import com.pgssoft.httpclient.Action; 5 | import com.pgssoft.httpclient.internal.action.ActionBundle; 6 | import com.pgssoft.httpclient.Condition; 7 | import com.pgssoft.httpclient.internal.condition.MethodCondition; 8 | import org.hamcrest.Matcher; 9 | import org.hamcrest.Matchers; 10 | 11 | import java.util.*; 12 | 13 | public final class RuleBuilder { 14 | 15 | private final Deque actionBundles = new LinkedList<>(); 16 | private final List conditions = new ArrayList<>(); 17 | private final UrlConditions urlConditions; 18 | 19 | public RuleBuilder(String method, String host, String url) { 20 | url = url.startsWith("/") ? host + url : url; 21 | addCondition(new MethodCondition(method)); 22 | urlConditions = UrlConditions.parse(url); 23 | } 24 | 25 | public RuleBuilder(String method) { 26 | addCondition(new MethodCondition(method)); 27 | urlConditions = new UrlConditions(); 28 | } 29 | 30 | public void addAction(Action action) { 31 | var bundle = actionBundles.peekLast(); 32 | if (bundle == null) { 33 | bundle = new ActionBundle(); 34 | actionBundles.add(bundle); 35 | } 36 | bundle.add(action); 37 | } 38 | 39 | public void addActionBundle(Action action) { 40 | final var bundle = new ActionBundle(); 41 | bundle.add(action); 42 | actionBundles.add(bundle); 43 | } 44 | 45 | public void addCondition(Condition condition) { 46 | conditions.add(condition); 47 | } 48 | 49 | 50 | public void setParameterCondition(String name, Matcher matcher) { 51 | urlConditions.getParameterConditions().addParam(name, Matchers.containsInAnyOrder(List.of(matcher))); 52 | } 53 | 54 | public void setReferenceCondition(Matcher matcher) { 55 | urlConditions.setReferenceCondition(matcher); 56 | } 57 | 58 | public void addHostCondition(String host) { 59 | urlConditions.setHostCondition(Matchers.equalTo(host)); 60 | } 61 | 62 | public void setPathCondition(Matcher matcher) { 63 | urlConditions.setPathCondition(matcher); 64 | } 65 | 66 | public Rule build() { 67 | return new Rule(urlConditions, conditions, actionBundles); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.pgssoft.httpclient { 2 | requires java.net.http; 3 | requires hamcrest.all; 4 | 5 | exports com.pgssoft.httpclient; 6 | } -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/DebuggerTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.debug.Debugger; 4 | import com.pgssoft.httpclient.internal.rule.Rule; 5 | import org.hamcrest.Matchers; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.net.URI; 10 | import java.net.http.HttpRequest; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static com.pgssoft.httpclient.TestRequests.*; 15 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 16 | import static java.net.http.HttpRequest.newBuilder; 17 | import static java.net.http.HttpResponse.BodyHandlers.discarding; 18 | import static java.net.http.HttpResponse.BodyHandlers.ofString; 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.Matchers.hasItem; 21 | import static org.hamcrest.Matchers.not; 22 | import static org.junit.jupiter.api.Assertions.*; 23 | 24 | public class DebuggerTest { 25 | 26 | private HttpClientMock httpClientMock; 27 | private TestDebugger debugger; 28 | 29 | @BeforeEach 30 | void setUp() { 31 | debugger = new TestDebugger(); 32 | httpClientMock = new HttpClientMock("http://localhost", debugger); 33 | } 34 | 35 | @Test 36 | void should_print_all_request_with_no_matching_rules() throws Exception { 37 | httpClientMock.onGet("/admin").doReturn("admin"); 38 | 39 | try { 40 | httpClientMock.send(get("http://localhost/login"), discarding()); 41 | httpClientMock.send(newBuilder(URI.create("http://localhost/admin")).GET().build(), discarding()); 42 | } catch (IllegalStateException e) { 43 | // discard exception 44 | } 45 | 46 | assertThat(debugger.requests, hasItem("http://localhost/login")); 47 | assertThat(debugger.requests, not(hasItem("http://localhost/admin"))); 48 | } 49 | 50 | @Test 51 | void should_print_all_request_when_debugging_is_turn_on() throws Exception { 52 | httpClientMock.onGet("/login").doReturn("login"); 53 | httpClientMock.onGet("/user").doReturn("user"); 54 | httpClientMock.onGet("/admin").doReturn("admin"); 55 | 56 | httpClientMock.debugOn(); 57 | httpClientMock.send(get("http://localhost/login"), discarding()); 58 | httpClientMock.send(newBuilder(URI.create("http://localhost/user")).GET().build(), discarding()); 59 | httpClientMock.debugOff(); 60 | httpClientMock.send(newBuilder(URI.create("http://localhost/admin")).GET().build(), discarding()); 61 | 62 | assertThat(debugger.requests, hasItem("http://localhost/login")); 63 | assertThat(debugger.requests, hasItem("http://localhost/user")); 64 | assertThat(debugger.requests, not(hasItem("http://localhost/admin"))); 65 | } 66 | 67 | @Test 68 | void should_debug_header_condition() throws Exception { 69 | httpClientMock 70 | .onGet("/login").withHeader("User-Agent", "Mozilla") 71 | .doReturn("mozilla"); 72 | 73 | try { 74 | httpClientMock.debugOn(); 75 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().header("User-Agent", "Mozilla").build(), ofString()); 76 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().header("User-Agent", "Chrome").build(), ofString()); 77 | httpClientMock.debugOff(); 78 | } catch (IllegalStateException e) { 79 | // discard the exception 80 | } 81 | 82 | assertTrue(debugger.matching.contains("header User-Agent is \"Mozilla\"")); 83 | assertFalse(debugger.notMatching.contains("header User-Agent is \"Chrome\"")); 84 | } 85 | 86 | @Test 87 | void should_put_message_about_missing_parameter() throws Exception { 88 | httpClientMock.onGet("/login").withParameter("foo", "bar"); 89 | try { 90 | httpClientMock.send(get("http://localhost/login"), discarding()); 91 | } catch (IllegalStateException e) { 92 | // discard exception 93 | } 94 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value")); 95 | } 96 | 97 | @Test 98 | void should_put_message_about_matching_parameter() throws Exception { 99 | httpClientMock 100 | .onGet("/login").withParameter("foo", "bar") 101 | .doReturn("login"); 102 | httpClientMock.debugOn(); 103 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bar")).GET().build(), discarding()); 104 | assertTrue(debugger.matching.contains("all URI parameters have matching value")); 105 | } 106 | 107 | @Test 108 | void should_put_message_about_not_matching_parameter() throws Exception { 109 | httpClientMock.onGet("/login") 110 | .withParameter("foo", "bar") 111 | .doReturn("login"); 112 | try { 113 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bbb")).GET().build(), discarding()); 114 | } catch (IllegalStateException e) { 115 | // discard exception 116 | } 117 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value")); 118 | } 119 | 120 | @Test 121 | void should_put_message_about_redundant_parameter() throws Exception { 122 | httpClientMock.onGet("/login") 123 | .doReturn("login"); 124 | try { 125 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=bbb")).GET().build(), discarding()); 126 | } catch (IllegalStateException e) { 127 | // discard exception 128 | } 129 | assertTrue(debugger.notMatching.contains("all URI parameters have matching value")); 130 | } 131 | 132 | @Test 133 | void should_put_message_with_all_parameter_matchers() throws Exception { 134 | httpClientMock.onGet("/login") 135 | .withParameter("foo", Matchers.allOf(Matchers.startsWith("a"), Matchers.endsWith("b"))) 136 | .doReturn("login"); 137 | httpClientMock.debugOn(); 138 | httpClientMock.send(newBuilder(URI.create("http://localhost/login?foo=aabb")).GET().build(), discarding()); 139 | assertTrue(debugger.matching.contains("all URI parameters have matching value")); 140 | } 141 | 142 | @Test 143 | void should_put_message_about_not_matching_reference() throws Exception { 144 | httpClientMock.onGet("/login#foo") 145 | .doReturn("login"); 146 | try { 147 | httpClientMock.send(get("http://localhost/login"), discarding()); 148 | } catch (IllegalStateException e) { 149 | // discard exception 150 | } 151 | assertTrue(debugger.notMatching.contains("URI reference has matching value")); 152 | } 153 | 154 | @Test 155 | void should_put_message_about_matching_reference() throws Exception { 156 | httpClientMock.onGet("/login#foo") 157 | .doReturn("login"); 158 | httpClientMock.debugOn(); 159 | httpClientMock.send(newBuilder(URI.create("http://localhost/login#foo")).GET().build(), discarding()); 160 | assertTrue(debugger.matching.contains("URI reference has matching value")); 161 | } 162 | 163 | 164 | @Test 165 | void should_put_message_about_matching_http_method() throws Exception { 166 | httpClientMock.onGet("/login").doReturn("login"); 167 | httpClientMock.debugOn(); 168 | httpClientMock.send(get("http://localhost/login"), discarding()); 169 | assertTrue(debugger.matching.contains("HTTP method is GET")); 170 | } 171 | 172 | @Test 173 | void should_put_message_about_not_matching_http_method() throws Exception { 174 | httpClientMock.onGet("/login").doReturn("login"); 175 | httpClientMock.debugOn(); 176 | try { 177 | httpClientMock.send(post("http://localhost/login"), discarding()); 178 | } catch (IllegalStateException e) { 179 | // discard exception 180 | } 181 | assertTrue(debugger.notMatching.contains("HTTP method is GET")); 182 | } 183 | 184 | @Test 185 | void should_put_message_about_not_matching_URL() throws Exception { 186 | httpClientMock.onGet("http://localhost:8080/login").doReturn("login"); 187 | httpClientMock.debugOn(); 188 | try { 189 | httpClientMock.send(newBuilder(URI.create("https://www.google.com")).POST(noBody()).build(), discarding()); 190 | } catch (IllegalStateException e) { 191 | // discard exception 192 | } 193 | assertTrue(debugger.notMatching.contains("schema is \"http\"")); 194 | assertTrue(debugger.notMatching.contains("host is \"localhost\"")); 195 | assertTrue(debugger.notMatching.contains("path is \"/login\"")); 196 | assertTrue(debugger.notMatching.contains("port is <8080>")); 197 | } 198 | 199 | @Test 200 | void should_put_message_about_matching_URL() { 201 | httpClientMock.onGet("http://localhost:8080/login").doReturn("login"); 202 | httpClientMock.debugOn(); 203 | 204 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost:8080/login"), discarding())); 205 | assertTrue(debugger.matching.contains("schema is \"http\"")); 206 | assertTrue(debugger.matching.contains("host is \"localhost\"")); 207 | assertTrue(debugger.matching.contains("path is \"/login\"")); 208 | assertTrue(debugger.matching.contains("port is <8080>")); 209 | } 210 | 211 | @Test 212 | void should_use_anonymous_message_for_conditions_without_debug_message() throws Exception { 213 | httpClientMock.onGet("http://localhost:8080/login") 214 | .with(new TestCondition()) 215 | .doReturn("login"); 216 | httpClientMock.debugOn(); 217 | try { 218 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding()); 219 | } catch (IllegalStateException e) { 220 | // discard exception 221 | } 222 | assertTrue(debugger.matching.contains("Anonymous condition")); 223 | } 224 | 225 | @Test 226 | void should_use_anonymous_message_for_lambda_conditions() throws Exception { 227 | httpClientMock.onGet("http://localhost:8080/login") 228 | .with(req -> true) 229 | .doReturn("login"); 230 | httpClientMock.debugOn(); 231 | try { 232 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding()); 233 | } catch (IllegalStateException e) { 234 | // discard exception 235 | } 236 | assertTrue(debugger.matching.contains("Anonymous condition")); 237 | } 238 | 239 | 240 | private class TestDebugger extends Debugger { 241 | private final ArrayList matching = new ArrayList<>(); 242 | private final ArrayList notMatching = new ArrayList<>(); 243 | private final ArrayList requests = new ArrayList<>(); 244 | 245 | @Override 246 | public void debug(List rules, HttpRequest request) { 247 | this.requests.add(request.uri().toString()); 248 | super.debug(rules, request); 249 | } 250 | 251 | @Override 252 | public void message(boolean matching, String expected) { 253 | super.message(matching, expected); 254 | if (matching) { 255 | this.matching.add(expected); 256 | } else { 257 | this.notMatching.add(expected); 258 | } 259 | } 260 | } 261 | } 262 | 263 | 264 | class TestCondition implements Condition { 265 | 266 | @Override 267 | public boolean matches(HttpRequest request) { 268 | return true; 269 | } 270 | } -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/HttpClientMockAsyncTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.net.URI; 6 | import java.net.http.HttpResponse; 7 | import java.util.concurrent.ExecutionException; 8 | 9 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 10 | import static java.net.http.HttpRequest.newBuilder; 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | 14 | class HttpClientMockAsyncTest { 15 | 16 | @Test 17 | void sendAsync_Should_ReturnCompletedFuture() throws ExecutionException, InterruptedException { 18 | HttpClientMock httpClientMock = new HttpClientMock(); 19 | 20 | httpClientMock.onPost() 21 | .withHost("localhost") 22 | .withPath("/login") 23 | .doReturn(200, "ABC"); 24 | 25 | var req = newBuilder(URI.create("http://localhost/login")) 26 | .POST(noBody()) 27 | .build(); 28 | var res = httpClientMock.sendAsync(req, HttpResponse.BodyHandlers.ofString()); 29 | 30 | assertThat(res.get().body(), equalTo("ABC")); 31 | assertThat(res.get().statusCode(), equalTo(200)); 32 | httpClientMock.verify().post("http://localhost/login"); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/HttpClientMockBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import org.hamcrest.MatcherAssert; 4 | import org.hamcrest.Matchers; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.IOException; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | import java.net.http.HttpClient; 11 | import java.net.http.HttpRequest; 12 | import java.net.http.HttpResponse; 13 | 14 | import static com.pgssoft.httpclient.TestRequests.get; 15 | import static com.pgssoft.httpclient.TestRequests.post; 16 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 17 | import static java.net.http.HttpRequest.newBuilder; 18 | import static java.net.http.HttpResponse.BodyHandlers.discarding; 19 | import static java.net.http.HttpResponse.BodyHandlers.ofString; 20 | import static org.hamcrest.CoreMatchers.equalTo; 21 | import static org.hamcrest.MatcherAssert.assertThat; 22 | import static org.hamcrest.Matchers.containsString; 23 | import static org.junit.jupiter.api.Assertions.assertThrows; 24 | import static org.junit.jupiter.api.Assertions.assertTrue; 25 | 26 | class HttpClientMockBuilderTest { 27 | 28 | @Test 29 | void shouldMatchSeparateHostAndPath() throws Exception { 30 | HttpClientMock httpClientMock = new HttpClientMock(); 31 | 32 | httpClientMock.onPost() 33 | .withHost("localhost") 34 | .withPath("/login") 35 | .doReturnStatus(200); 36 | 37 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://www.google.com/login"), discarding())); 38 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/foo"), discarding())); 39 | assertThat(httpClientMock.send(post("http://localhost/login"), discarding()).statusCode(), equalTo(200)); 40 | } 41 | 42 | @Test 43 | void shouldMatchSeparatePathAndParameter() throws Exception { 44 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 45 | 46 | httpClientMock.onPost() 47 | .withPath("/login") 48 | .withParameter("a", "1") 49 | .doReturn("one"); 50 | httpClientMock.onPost() 51 | .withPath("/login") 52 | .withParameter("b", "2") 53 | .doReturn("two"); 54 | 55 | var firstResponse = httpClientMock.send( 56 | newBuilder(URI.create("http://localhost/login?a=1")) 57 | .POST(noBody()) 58 | .build(), 59 | ofString() 60 | ); 61 | 62 | var secondResponse = httpClientMock.send( 63 | newBuilder(URI.create("http://localhost/login?b=2")) 64 | .POST(noBody()) 65 | .build(), 66 | ofString() 67 | ); 68 | 69 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login?a=1&b=2"), ofString())); 70 | 71 | MatcherAssert.assertThat(firstResponse, HttpResponseMatchers.hasContent("one")); 72 | MatcherAssert.assertThat(secondResponse, HttpResponseMatchers.hasContent("two")); 73 | } 74 | 75 | @Test 76 | void onXXXX_methods_with_path_should_add_method_and_path_condition() throws Exception { 77 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 78 | 79 | httpClientMock.onGet("/foo").doReturn("get"); 80 | httpClientMock.onPost("/foo").doReturn("post"); 81 | httpClientMock.onPut("/foo").doReturn("put"); 82 | httpClientMock.onDelete("/foo").doReturn("delete"); 83 | httpClientMock.onHead("/foo").doReturn("head"); 84 | httpClientMock.onOptions("/foo").doReturn("options"); 85 | httpClientMock.onPatch("/foo").doReturn("patch"); 86 | 87 | var getResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 88 | var postResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).POST(noBody()).build(), ofString()); 89 | var putResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).PUT(noBody()).build(), ofString()); 90 | var deleteResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).DELETE().build(), ofString()); 91 | var headResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("HEAD", noBody()).build(), ofString()); 92 | var optionsResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("OPTIONS", noBody()).build(), ofString()); 93 | var patchResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("PATCH", noBody()).build(), ofString()); 94 | 95 | MatcherAssert.assertThat(getResponse, HttpResponseMatchers.hasContent("get")); 96 | MatcherAssert.assertThat(postResponse, HttpResponseMatchers.hasContent("post")); 97 | MatcherAssert.assertThat(putResponse, HttpResponseMatchers.hasContent("put")); 98 | MatcherAssert.assertThat(deleteResponse, HttpResponseMatchers.hasContent("delete")); 99 | MatcherAssert.assertThat(headResponse, HttpResponseMatchers.hasContent("head")); 100 | MatcherAssert.assertThat(optionsResponse, HttpResponseMatchers.hasContent("options")); 101 | MatcherAssert.assertThat(patchResponse, HttpResponseMatchers.hasContent("patch")); 102 | } 103 | 104 | 105 | @Test 106 | void onXXXX_methods_should_add_method_condition() throws Exception { 107 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 108 | 109 | httpClientMock.onGet().doReturn("get"); 110 | httpClientMock.onPost().doReturn("post"); 111 | httpClientMock.onPut().doReturn("put"); 112 | httpClientMock.onDelete().doReturn("delete"); 113 | httpClientMock.onHead().doReturn("head"); 114 | httpClientMock.onOptions().doReturn("options"); 115 | httpClientMock.onPatch().doReturn("patch"); 116 | 117 | var getResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 118 | var postResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).POST(noBody()).build(), ofString()); 119 | var putResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).PUT(noBody()).build(), ofString()); 120 | var deleteResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).DELETE().build(), ofString()); 121 | var headResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("HEAD", noBody()).build(), ofString()); 122 | var optionsResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("OPTIONS", noBody()).build(), ofString()); 123 | var patchResponse = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).method("PATCH", noBody()).build(), ofString()); 124 | 125 | MatcherAssert.assertThat(getResponse, HttpResponseMatchers.hasContent("get")); 126 | MatcherAssert.assertThat(postResponse, HttpResponseMatchers.hasContent("post")); 127 | MatcherAssert.assertThat(putResponse, HttpResponseMatchers.hasContent("put")); 128 | MatcherAssert.assertThat(deleteResponse, HttpResponseMatchers.hasContent("delete")); 129 | MatcherAssert.assertThat(headResponse, HttpResponseMatchers.hasContent("head")); 130 | MatcherAssert.assertThat(optionsResponse, HttpResponseMatchers.hasContent("options")); 131 | MatcherAssert.assertThat(patchResponse, HttpResponseMatchers.hasContent("patch")); 132 | } 133 | 134 | @Test 135 | void shouldCheckCustomRule() throws Exception { 136 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 137 | 138 | final Condition fooCondition = request -> request.uri().toString().contains("foo"); 139 | httpClientMock.onGet("http://localhost/foo/bar") 140 | .with(fooCondition) 141 | .doReturn("yes"); 142 | 143 | final HttpResponse first = httpClientMock.send(newBuilder(URI.create("http://localhost/foo/bar")).GET().build(), ofString()); 144 | 145 | MatcherAssert.assertThat(first, HttpResponseMatchers.hasContent("yes")); 146 | } 147 | 148 | @Test 149 | void shouldUseRightHostAndPath() throws Exception { 150 | HttpClientMock httpClientMock = new HttpClientMock(); 151 | 152 | httpClientMock.onGet("http://localhost:8080/foo").doReturn("localhost"); 153 | httpClientMock.onGet("http://www.google.com").doReturn("google"); 154 | httpClientMock.onGet("https://www.google.com").doReturn("https"); 155 | 156 | final HttpResponse localhost = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString()); 157 | final HttpResponse google = httpClientMock.send(get("http://www.google.com"), ofString()); 158 | final HttpResponse https = httpClientMock.send(newBuilder(URI.create("https://www.google.com")).GET().build(), ofString()); 159 | 160 | MatcherAssert.assertThat(localhost, HttpResponseMatchers.hasContent("localhost")); 161 | MatcherAssert.assertThat(google, HttpResponseMatchers.hasContent("google")); 162 | MatcherAssert.assertThat(https, HttpResponseMatchers.hasContent("https")); 163 | } 164 | 165 | @Test 166 | void shouldMatchRightHeaderValue() throws Exception { 167 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 168 | 169 | httpClientMock 170 | .onGet("/login").withHeader("User-Agent", "Mozilla") 171 | .doReturn("mozilla"); 172 | httpClientMock 173 | .onGet("/login").withHeader("User-Agent", "Chrome") 174 | .doReturn("chrome"); 175 | 176 | final var getMozilla = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Mozilla").build(), ofString()); 177 | final var getChrome = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Chrome").build(), ofString()); 178 | //final var getSafari = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Safari").build(), ofString()); 179 | 180 | MatcherAssert.assertThat(getMozilla, HttpResponseMatchers.hasContent("mozilla")); 181 | MatcherAssert.assertThat(getChrome, HttpResponseMatchers.hasContent("chrome")); 182 | //assertThat(httpClientMock.execute(getSafari), hasStatus(404)); 183 | } 184 | 185 | @Test 186 | void should_match_right_parameter_value() throws Exception { 187 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 188 | 189 | httpClientMock 190 | .onGet("/foo").withParameter("id", "1").withParameter("name", "abc") 191 | .doReturn("one"); 192 | httpClientMock 193 | .onGet("/foo").withParameter("id", "2") 194 | .doReturn("two"); 195 | 196 | final var one = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo?id=1&name=abc")).GET().build(), ofString()); 197 | final var two = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo?id=2")).GET().build(), ofString()); 198 | 199 | MatcherAssert.assertThat(one, HttpResponseMatchers.hasContent("one")); 200 | MatcherAssert.assertThat(two, HttpResponseMatchers.hasContent("two")); 201 | } 202 | 203 | @Test 204 | void should_add_default_host_to_every_relative_path() throws Exception { 205 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 206 | 207 | httpClientMock.onGet("/login").doReturn("login"); 208 | httpClientMock.onGet("/product/search").doReturn("search"); 209 | httpClientMock.onGet("/logout").doReturn("logout"); 210 | 211 | final var login = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), ofString()); 212 | final var search = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/product/search")).GET().build(), ofString()); 213 | final var logout = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/logout")).GET().build(), ofString()); 214 | 215 | MatcherAssert.assertThat(login, HttpResponseMatchers.hasContent("login")); 216 | MatcherAssert.assertThat(search, HttpResponseMatchers.hasContent("search")); 217 | MatcherAssert.assertThat(logout, HttpResponseMatchers.hasContent("logout")); 218 | } 219 | 220 | @Test 221 | void checkBody() throws Exception { 222 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 223 | 224 | httpClientMock.onPost("/login") 225 | .doReturnStatus(500); 226 | httpClientMock.onPost("/login").withBody(containsString("foo")) 227 | .doReturnStatus(200); 228 | 229 | final var badLogin = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), discarding()); 230 | final var correctLogin = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), discarding()); 231 | 232 | MatcherAssert.assertThat(correctLogin, HttpResponseMatchers.hasStatus(200)); 233 | MatcherAssert.assertThat(badLogin, HttpResponseMatchers.hasStatus(500)); 234 | } 235 | 236 | @Test 237 | void when_url_contains_parameter_it_should_be_added_us_a_separate_condition() throws Exception { 238 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 239 | 240 | httpClientMock.onPost("/login?user=john") 241 | .doReturnStatus(400); 242 | httpClientMock.onPost("/login?user=john&pass=abc") 243 | .doReturnStatus(200); 244 | 245 | // HttpResponse notFound = httpClientMock.execute(new HttpPost("http://localhost/login")); 246 | // HttpResponse notFound_2 = httpClientMock.execute(new HttpPost("http://localhost/login?user=john&pass=abc&foo=bar")); 247 | 248 | final var wrong = httpClientMock.send(post("http://localhost/login?user=john"), discarding()); 249 | final var ok = httpClientMock.send(post("http://localhost/login?user=john&pass=abc"), discarding()); 250 | 251 | //assertThat(notFound, hasStatus(404)); 252 | MatcherAssert.assertThat(wrong, HttpResponseMatchers.hasStatus(400)); 253 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200)); 254 | //assertThat(notFound_2, hasStatus(404)); 255 | } 256 | 257 | @Test 258 | void should_not_match_URL_with_missing_param() { 259 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 260 | 261 | httpClientMock.onPost("/login?user=john") 262 | .doReturnStatus(400); 263 | httpClientMock.onPost("/login?user=john&pass=abc") 264 | .doReturnStatus(200); 265 | var request = newBuilder(URI.create("http://localhost/login")).POST(noBody()).build(); 266 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(request, discarding())); 267 | } 268 | 269 | @Test 270 | void should_not_match_URL_with_surplus_param() { 271 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 272 | 273 | httpClientMock.onPost("/login?user=john") 274 | .doReturnStatus(200); 275 | var request = newBuilder(URI.create("http://localhost/login?user=john&pass=abc")).POST(noBody()).build(); 276 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(request, discarding())); 277 | } 278 | 279 | 280 | @Test 281 | void should_handle_path_with_parameters_and_reference() throws Exception { 282 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 283 | 284 | httpClientMock.onPost("/login?p=1#abc") 285 | .doReturnStatus(200); 286 | 287 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), discarding())); 288 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login?p=1"), discarding())); 289 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login#abc"), discarding())); 290 | 291 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost/login?p=1#abc")).POST(noBody()).build(), discarding()); 292 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200)); 293 | } 294 | 295 | @Test 296 | void should_check_reference_value() throws Exception { 297 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 298 | 299 | httpClientMock.onPost("/login") 300 | .doReturnStatus(400); 301 | httpClientMock.onPost("/login") 302 | .withReference("ref") 303 | .doReturnStatus(200); 304 | 305 | final var wrong = httpClientMock.send(newBuilder(URI.create("http://localhost/login")).POST(noBody()).build(), discarding()); 306 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost/login#ref")).POST(noBody()).build(), discarding()); 307 | 308 | MatcherAssert.assertThat(wrong, HttpResponseMatchers.hasStatus(400)); 309 | MatcherAssert.assertThat(ok, HttpResponseMatchers.hasStatus(200)); 310 | } 311 | 312 | @Test 313 | void after_reset_every_call_should_throw_exception() { 314 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 315 | 316 | httpClientMock.onPost("/login").doReturnStatus(200); 317 | httpClientMock.reset(); 318 | 319 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), discarding())); 320 | } 321 | 322 | @Test 323 | void after_reset_number_of_calls_should_be_zero() throws Exception { 324 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 325 | 326 | httpClientMock.onPost("/login").doReturnStatus(200); 327 | httpClientMock.send(post("http://localhost/login"), discarding()); 328 | httpClientMock.send(post("http://localhost/login"), discarding()); 329 | httpClientMock.reset(); 330 | httpClientMock.verify().post("/login").notCalled(); 331 | 332 | httpClientMock.onPost("/login").doReturnStatus(200); 333 | httpClientMock.send(post("http://localhost/login"), discarding()); 334 | httpClientMock.send(post("http://localhost/login"), discarding()); 335 | httpClientMock.verify().post("/login").called(2); 336 | 337 | } 338 | 339 | @Test 340 | void not_all_parameters_occurred() { 341 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 342 | 343 | httpClientMock.onPost("/login") 344 | .withParameter("foo", "bar") 345 | .doReturnStatus(200); 346 | 347 | assertThrows(NoMatchingRuleException.class, () -> httpClientMock.send(post("http://localhost/login"), ofString())); 348 | } 349 | 350 | @Test 351 | void should_allow_different_host_then_default() throws Exception { 352 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 353 | 354 | httpClientMock.onGet("/login").doReturn("login"); 355 | httpClientMock.onGet("http://www.google.com").doReturn("google"); 356 | 357 | final var login = httpClientMock.send(get("http://localhost/login"), ofString()); 358 | final var google = httpClientMock.send(get("http://www.google.com"), ofString()); 359 | 360 | MatcherAssert.assertThat(login, HttpResponseMatchers.hasContent("login")); 361 | MatcherAssert.assertThat(google, HttpResponseMatchers.hasContent("google")); 362 | } 363 | 364 | @Test 365 | void response_should_contain_request_headers_body_uri_version() throws IOException, URISyntaxException { 366 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 367 | httpClientMock.onGet("/login") 368 | .doReturn("login") 369 | .withHeader("foo", "bar"); 370 | var request = get("http://localhost/login"); 371 | var response = httpClientMock.send(request, ofString()); 372 | assertThat(response.uri(), Matchers.equalTo(new URI("http://localhost/login"))); 373 | assertThat(response.request(), Matchers.equalTo(request)); 374 | assertThat(response.body(), Matchers.equalTo("login")); 375 | assertTrue(response.headers().firstValue("foo").isPresent()); 376 | assertThat(response.headers().firstValue("foo").get(), Matchers.equalTo("bar")); 377 | assertThat(response.version(), Matchers.equalTo(HttpClient.Version.HTTP_1_1)); 378 | 379 | } 380 | 381 | 382 | } 383 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/HttpClientResponseBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.net.URI; 10 | import java.net.http.HttpResponse.BodyHandlers; 11 | import java.nio.ByteBuffer; 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | import static com.pgssoft.httpclient.HttpResponseMatchers.hasContent; 18 | import static com.pgssoft.httpclient.HttpResponseMatchers.hasStatus; 19 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 20 | import static java.net.http.HttpRequest.newBuilder; 21 | import static java.net.http.HttpResponse.BodyHandlers.discarding; 22 | import static java.net.http.HttpResponse.BodyHandlers.ofString; 23 | import static org.hamcrest.MatcherAssert.assertThat; 24 | import static org.hamcrest.Matchers.equalTo; 25 | import static org.hamcrest.Matchers.not; 26 | import static org.junit.jupiter.api.Assertions.*; 27 | 28 | class HttpClientResponseBuilderTest { 29 | 30 | @Test 31 | void should_throw_exception_when_no_rule_matches() { 32 | assertThrows(IllegalStateException.class, () -> { 33 | HttpClientMock httpClientMock = new HttpClientMock(); 34 | httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 35 | }); 36 | } 37 | 38 | @Test 39 | void should_use_next_action_after_every_call() throws Exception { 40 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 41 | 42 | httpClientMock.onGet("/foo") 43 | .doReturn("first") 44 | .doReturn("second") 45 | .doReturn("third"); 46 | 47 | httpClientMock.onGet("/bar") 48 | .doReturn("bar") 49 | .doReturnStatus(300) 50 | .doThrowException(new IOException()); 51 | 52 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 53 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 54 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 55 | final var response4 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 56 | final var response5 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 57 | 58 | assertThat(response1, hasContent("first")); 59 | assertThat(response2, hasContent("second")); 60 | assertThat(response3, hasContent("third")); 61 | assertThat(response4, hasContent("third")); 62 | assertThat(response5, hasContent("third")); 63 | 64 | final var bar1 = httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), ofString()); 65 | final var bar2 = httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), discarding()); 66 | assertThat(bar1, hasContent("bar")); 67 | assertThat(bar2, hasStatus(300)); 68 | 69 | assertThrows(IOException.class, () -> httpClientMock.send(newBuilder(URI.create("http://localhost/bar")).GET().build(), ofString())); 70 | 71 | } 72 | 73 | @Test 74 | void should_support_response_in_different_charsets() throws Exception { 75 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 76 | 77 | httpClientMock.onGet("/foo") 78 | .doReturn("first") 79 | .doReturn("second", Charset.forName("UTF-16")) 80 | .doReturn("third", Charset.forName("ASCII")); 81 | 82 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 83 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.UTF_16)); 84 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.US_ASCII)); 85 | 86 | assertThat(response1, hasContent("first")); 87 | assertThat(response2, hasContent("second")); 88 | assertThat(response3, hasContent("third")); 89 | } 90 | 91 | @Test 92 | void shouldFailToDecodeBodyWhenDifferentCharsetsUsed() throws Exception { 93 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 94 | httpClientMock.onGet("/foo").doReturn("output", StandardCharsets.UTF_16); 95 | 96 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString(StandardCharsets.UTF_8)); 97 | 98 | assertThat(response1, not(hasContent("output"))); 99 | } 100 | 101 | @Test 102 | void should_support_response_in_body_with_status() throws Exception { 103 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 104 | 105 | httpClientMock.onGet("/foo") 106 | .doReturn("first") 107 | .doReturn(300, "second") 108 | .doReturn(400, "third"); 109 | 110 | final var response1 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 111 | final var response2 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 112 | final var response3 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 113 | final var response4 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 114 | final var response5 = httpClientMock.send(newBuilder(URI.create("http://localhost/foo")).GET().build(), ofString()); 115 | 116 | assertThat(response1, hasContent("first")); 117 | assertThat(response1, hasStatus(200)); 118 | assertThat(response2, hasContent("second")); 119 | assertThat(response2, hasStatus(300)); 120 | assertThat(response3, hasContent("third")); 121 | assertThat(response3, hasStatus(400)); 122 | assertThat(response4, hasContent("third")); 123 | assertThat(response4, hasStatus(400)); 124 | assertThat(response5, hasContent("third")); 125 | assertThat(response5, hasStatus(400)); 126 | } 127 | 128 | @Test 129 | void should_throw_exception_when_throwing_action_matched() { 130 | assertThrows(IOException.class, () -> { 131 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 132 | httpClientMock.onGet("/foo").doThrowException(new IOException()); 133 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), discarding()); 134 | }); 135 | } 136 | 137 | @Test 138 | void should_return_status_corresponding_to_match() throws Exception { 139 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 140 | 141 | httpClientMock.onGet("/login").doReturnStatus(200); 142 | httpClientMock.onGet("/abc").doReturnStatus(404); 143 | httpClientMock.onGet("/error").doReturnStatus(500); 144 | 145 | final var ok = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), discarding()); 146 | final var notFound = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/abc")).GET().build(), discarding()); 147 | final var error = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/error")).GET().build(), discarding()); 148 | 149 | assertThat(ok, hasStatus(200)); 150 | assertThat(notFound, hasStatus(404)); 151 | assertThat(error, hasStatus(500)); 152 | } 153 | 154 | @Test 155 | void should_do_custom_action() throws Exception { 156 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 157 | httpClientMock.onPost("/login").doAction(customAction()); 158 | 159 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString()); 160 | 161 | assertThat(response, hasContent("I am a custom action")); 162 | 163 | } 164 | 165 | @Test 166 | void should_add_header_to_response() throws Exception { 167 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 168 | httpClientMock.onPost("/login") 169 | .doReturn("foo").withHeader("tracking", "123") 170 | .doReturn("foo").withHeader("tracking", "456"); 171 | 172 | final var first = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString()); 173 | final var second = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).POST(noBody()).build(), ofString()); 174 | 175 | assertThat(first.headers().firstValue("tracking").orElse(null), equalTo("123")); 176 | assertThat(second.headers().firstValue("tracking").orElse(null), equalTo("456")); 177 | } 178 | 179 | @Test 180 | void should_add_status_to_response() throws Exception { 181 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 182 | httpClientMock.onGet("/login") 183 | .doReturn("foo").withStatus(300); 184 | 185 | final var login = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().build(), ofString()); 186 | 187 | assertThat(login, hasContent("foo")); 188 | assertThat(login, hasStatus(300)); 189 | 190 | } 191 | 192 | @Test 193 | void should_return_json_with_right_header() throws Exception { 194 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 195 | httpClientMock.onGet("/foo") 196 | .doReturnJSON("{foo:1}", Charset.forName("UTF-16")); 197 | httpClientMock.onGet("/bar") 198 | .doReturnJSON("{bar:1}"); 199 | 200 | final var foo = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString()); 201 | final var bar = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/bar")).GET().build(), ofString()); 202 | assertThat(foo, hasContent("{foo:1}")); 203 | assertTrue(foo.headers().firstValue("Content-type").isPresent()); 204 | assertThat(foo.headers().firstValue("Content-type").get(), equalTo("application/json; charset=UTF-16")); 205 | 206 | assertThat(bar, hasContent("{bar:1}")); 207 | assertTrue(bar.headers().firstValue("Content-type").isPresent()); 208 | assertThat(bar.headers().firstValue("Content-type").get(), equalTo("application/json; charset=UTF-8")); 209 | } 210 | 211 | @Test 212 | void should_return_xml_with_right_header() throws Exception { 213 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 214 | httpClientMock.onGet("/foo") 215 | .doReturnXML("bar", Charset.forName("UTF-16")); 216 | httpClientMock.onGet("/bar") 217 | .doReturnXML("bar"); 218 | final var foo = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/foo")).GET().build(), ofString()); 219 | final var bar = httpClientMock.send(newBuilder(URI.create("http://localhost:8080/bar")).GET().build(), ofString()); 220 | 221 | assertThat(foo, hasContent("bar")); 222 | assertThat(foo.headers().firstValue("Content-type").orElse(null), equalTo("application/xml; charset=UTF-16")); 223 | 224 | assertThat(bar, hasContent("bar")); 225 | assertThat(bar.headers().firstValue("Content-type").orElse(null), equalTo("application/xml; charset=UTF-8")); 226 | } 227 | 228 | @Test 229 | void should_allow_to_return_response_without_body() throws Exception { 230 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 231 | httpClientMock.onGet("/login") 232 | .doReturnStatus(204); // no content 233 | var request = newBuilder(URI.create("http://localhost:8080/login")).GET().build(); 234 | var response = httpClientMock.send(request, BodyHandlers.ofString()); 235 | 236 | assertEquals("", response.body()); 237 | } 238 | 239 | @Test 240 | void should_throw_exception_when_body_matcher_is_present_on_post_request() { 241 | assertThrows(IllegalStateException.class, () -> { 242 | HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 243 | httpClientMock.onPost("/path1") 244 | .withBody(equalTo("Body content")) 245 | .doReturnStatus(200); 246 | 247 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/path2")).GET().build(), discarding()); 248 | }); 249 | } 250 | 251 | @Test 252 | void shouldRespectBodyHandler() throws Exception { 253 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 254 | // A simple string is passed to indicate what we want to see in the response 255 | httpClientMock.onGet().doReturn("expected\nnewline"); 256 | 257 | // A BodyHandler is passed that transforms into a Stream of String based on newline characters 258 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), BodyHandlers.ofLines()); 259 | 260 | // We expect out String to be returned in the form that the BodyHandler requires - a Stream of Strings 261 | final List responseList = response.body().collect(Collectors.toList()); 262 | assertThat(responseList.size(), equalTo(2)); 263 | assertThat(responseList.get(0), equalTo("expected")); 264 | assertThat(responseList.get(1), equalTo("newline")); 265 | } 266 | 267 | @Test 268 | void shouldTransformStringToInputStream() throws Exception { 269 | final String expectedString = "expected"; 270 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 271 | httpClientMock.onGet().doReturn(expectedString); 272 | 273 | final var response = httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), BodyHandlers.ofInputStream()); 274 | 275 | final InputStream output = response.body(); 276 | final String outputString = new BufferedReader(new InputStreamReader(output)).readLine(); 277 | assertThat(outputString, equalTo(expectedString)); 278 | } 279 | 280 | private Action customAction() { 281 | return r -> r.setBodyBytes(ByteBuffer.wrap("I am a custom action".getBytes())); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/HttpClientVerifyTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.net.URI; 7 | import java.net.http.HttpRequest; 8 | 9 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 10 | import static java.net.http.HttpRequest.newBuilder; 11 | import static java.net.http.HttpResponse.BodyHandlers.discarding; 12 | import static java.net.http.HttpResponse.BodyHandlers.ofString; 13 | import static org.hamcrest.Matchers.containsString; 14 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 15 | 16 | class HttpClientVerifyTest { 17 | 18 | @Test 19 | void shouldHandleAllHttpMethodsWithURL() throws Exception { 20 | 21 | final HttpClientMock httpClientMock = new HttpClientMock(); 22 | httpClientMock.onGet("http://localhost").doReturn("empty"); 23 | httpClientMock.onPost("http://localhost").doReturn("empty"); 24 | httpClientMock.onPut("http://localhost").doReturn("empty"); 25 | httpClientMock.onDelete("http://localhost").doReturn("empty"); 26 | httpClientMock.onHead("http://localhost").doReturn("empty"); 27 | httpClientMock.onOptions("http://localhost").doReturn("empty"); 28 | httpClientMock.onPatch("http://localhost").doReturn("empty"); 29 | 30 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding()); 31 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding()); 32 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding()); 33 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(noBody()).build(), discarding()); 34 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("HEAD", noBody()).build(), discarding()); 35 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("OPTIONS", noBody()).build(), discarding()); 36 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("PATCH", noBody()).build(), discarding()); 37 | 38 | httpClientMock.verify().get("http://localhost").called(); 39 | httpClientMock.verify().post("http://localhost").called(); 40 | httpClientMock.verify().delete("http://localhost").called(); 41 | httpClientMock.verify().put("http://localhost").called(); 42 | httpClientMock.verify().options("http://localhost").called(); 43 | httpClientMock.verify().head("http://localhost").called(); 44 | httpClientMock.verify().patch("http://localhost").called(); 45 | } 46 | 47 | @Test 48 | void shouldHandleAllHttpMethodsWithoutURL() throws Exception { 49 | final HttpClientMock httpClientMock = new HttpClientMock(); 50 | httpClientMock.onGet("http://localhost").doReturn("empty"); 51 | httpClientMock.onPost("http://localhost").doReturn("empty"); 52 | httpClientMock.onPut("http://localhost").doReturn("empty"); 53 | httpClientMock.onDelete("http://localhost").doReturn("empty"); 54 | httpClientMock.onHead("http://localhost").doReturn("empty"); 55 | httpClientMock.onOptions("http://localhost").doReturn("empty"); 56 | httpClientMock.onPatch("http://localhost").doReturn("empty"); 57 | 58 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding()); 59 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding()); 60 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding()); 61 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(noBody()).build(), discarding()); 62 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("HEAD", noBody()).build(), discarding()); 63 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("OPTIONS", noBody()).build(), discarding()); 64 | httpClientMock.send(newBuilder(URI.create("http://localhost")).method("PATCH", noBody()).build(), discarding()); 65 | 66 | httpClientMock.verify().get().called(); 67 | httpClientMock.verify().post().called(); 68 | httpClientMock.verify().delete().called(); 69 | httpClientMock.verify().put().called(); 70 | httpClientMock.verify().options().called(); 71 | httpClientMock.verify().head().called(); 72 | httpClientMock.verify().patch().called(); 73 | } 74 | 75 | @Test 76 | void shouldCountNumberOfHttpMethodCalls() throws Exception { 77 | final HttpClientMock httpClientMock = new HttpClientMock(); 78 | 79 | httpClientMock.onGet("http://localhost").doReturn("empty"); 80 | httpClientMock.onPost("http://localhost").doReturn("empty"); 81 | httpClientMock.onPost("http://localhost").doReturn("empty"); 82 | httpClientMock.onDelete("http://localhost").doReturn("empty"); 83 | httpClientMock.onDelete("http://localhost").doReturn("empty"); 84 | httpClientMock.onDelete("http://localhost").doReturn("empty"); 85 | 86 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding()); 87 | 88 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding()); 89 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(noBody()).build(), discarding()); 90 | 91 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding()); 92 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding()); 93 | httpClientMock.send(newBuilder(URI.create("http://localhost")).DELETE().build(), discarding()); 94 | 95 | httpClientMock.verify().get("http://localhost").called(); 96 | httpClientMock.verify().post("http://localhost").called(2); 97 | httpClientMock.verify().delete("http://localhost").called(3); 98 | 99 | httpClientMock.verify().get().called(greaterThanOrEqualTo(1)); 100 | httpClientMock.verify().post().called(greaterThanOrEqualTo(1)); 101 | httpClientMock.verify().delete().called(greaterThanOrEqualTo(1)); 102 | } 103 | 104 | @Test 105 | void shouldCountNumberOfUrlCalls() throws Exception { 106 | final HttpClientMock httpClientMock = new HttpClientMock(); 107 | httpClientMock.onGet("http://localhost").doReturn("empty"); 108 | httpClientMock.onGet("http://www.google.com").doReturn("empty"); 109 | httpClientMock.onGet("http://www.google.com").doReturn("empty"); 110 | httpClientMock.onGet("http://example.com").doReturn("empty"); 111 | httpClientMock.onGet("http://example.com").doReturn("empty"); 112 | httpClientMock.onGet("http://example.com").doReturn("empty"); 113 | 114 | httpClientMock.send(newBuilder(URI.create("http://localhost")).GET().build(), discarding()); 115 | 116 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding()); 117 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding()); 118 | 119 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding()); 120 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding()); 121 | httpClientMock.send(newBuilder(URI.create("http://example.com")).GET().build(), discarding()); 122 | 123 | httpClientMock.verify().get("http://localhost").called(); 124 | httpClientMock.verify().get("http://www.google.com").called(2); 125 | httpClientMock.verify().get("http://example.com").called(3); 126 | } 127 | 128 | @Test 129 | void shouldVerifyBodyContent() throws Exception { 130 | final HttpClientMock httpClientMock = new HttpClientMock(); 131 | httpClientMock.onPost("http://localhost").withBody(containsString("foo")).doReturn("empty"); 132 | httpClientMock.onPost("http://localhost").withBody(containsString("foo")).doReturn("empty"); 133 | httpClientMock.onPut("http://localhost").withBody(containsString("bar")).doReturn("empty"); 134 | httpClientMock.onPut("http://localhost").withBody(containsString("foo")).doReturn("empty"); 135 | 136 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString()); 137 | httpClientMock.send(newBuilder(URI.create("http://localhost")).POST(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString()); 138 | 139 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(HttpRequest.BodyPublishers.ofString("bar")).build(), ofString()); 140 | httpClientMock.send(newBuilder(URI.create("http://localhost")).PUT(HttpRequest.BodyPublishers.ofString("foo")).build(), ofString()); 141 | 142 | httpClientMock.verify().post("http://localhost").withBody(containsString("foo")).called(2); 143 | httpClientMock.verify().put("http://localhost").withBody(containsString("bar")).called(); 144 | httpClientMock.verify().get("http://localhost").withBody(containsString("foo bar")).notCalled(); 145 | } 146 | 147 | @Test 148 | void should_handle_path_with_query_parameter() throws Exception { 149 | final HttpClientMock httpClientMock = new HttpClientMock(); 150 | httpClientMock.onPost("http://localhost").withParameter("a", "1").withParameter("b", "2").withParameter("c", "3").doReturn("empty"); 151 | httpClientMock.onPost("http://localhost").withParameter("a", "1").withParameter("b", "2").doReturn("empty"); 152 | httpClientMock.onPost("http://localhost").withParameter("a", "1").doReturn("empty"); 153 | 154 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1&b=2&c=3")).POST(noBody()).build(), discarding()); 155 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1&b=2")).POST(noBody()).build(), discarding()); 156 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1")).POST(noBody()).build(), discarding()); 157 | 158 | httpClientMock.verify().post("http://localhost?d=3").notCalled(); 159 | httpClientMock.verify().post("http://localhost?a=3").notCalled(); 160 | httpClientMock.verify().post("http://localhost?a=1&b=2&c=3").called(1); 161 | httpClientMock.verify().post("http://localhost?a=1&b=2").called(1); 162 | httpClientMock.verify().post("http://localhost?a=1").called(1); 163 | httpClientMock.verify().post("http://localhost").withParameter("a", "1").called(1); 164 | 165 | httpClientMock.verify().post("http://localhost").notCalled(); 166 | } 167 | 168 | @Test 169 | void should_handle_path_with_reference() throws Exception { 170 | final HttpClientMock httpClientMock = new HttpClientMock(); 171 | httpClientMock.onPost().withParameter("a", "1").withReference("abc").doReturn("empty"); 172 | httpClientMock.onPost().withReference("xyz").doReturn("empty"); 173 | 174 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1#abc")).POST(noBody()).build(), discarding()); 175 | httpClientMock.send(newBuilder(URI.create("http://localhost#xyz")).POST(noBody()).build(), discarding()); 176 | 177 | httpClientMock.verify().post("http://localhost?a=1#abc").called(1); 178 | httpClientMock.verify().post("http://localhost#abc").notCalled(); 179 | httpClientMock.verify().post("http://localhost#xyz").called(1); 180 | httpClientMock.verify().post("http://localhost").notCalled(); 181 | } 182 | 183 | @Test 184 | void should_throw_exception_when_number_of_calls_is_wrong() { 185 | Assertions.assertThrows(IllegalStateException.class, () -> { 186 | final HttpClientMock httpClientMock = new HttpClientMock(); 187 | 188 | httpClientMock.send(newBuilder(URI.create("http://localhost?a=1")).POST(noBody()).build(), discarding()); 189 | 190 | httpClientMock.verify() 191 | .post("http://localhost?a=1#abc") 192 | .called(2); 193 | }); 194 | } 195 | 196 | @Test 197 | void should_allow_different_host_then_default() throws Exception { 198 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost"); 199 | 200 | httpClientMock.onGet("/login").doReturn("login"); 201 | httpClientMock.onGet("http://www.google.com").doReturn("google"); 202 | 203 | httpClientMock.send(newBuilder(URI.create("http://localhost/login")).GET().build(), discarding()); 204 | httpClientMock.send(newBuilder(URI.create("http://www.google.com")).GET().build(), discarding()); 205 | 206 | httpClientMock.verify().get("/login").called(); 207 | httpClientMock.verify().get("http://www.google.com").called(); 208 | } 209 | 210 | @Test 211 | void should_check_header() throws Exception { 212 | final HttpClientMock httpClientMock = new HttpClientMock("http://localhost:8080"); 213 | 214 | httpClientMock.onGet("/login").doReturn("OK"); 215 | 216 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Chrome").build(), discarding()); 217 | httpClientMock.send(newBuilder(URI.create("http://localhost:8080/login")).GET().header("User-Agent", "Mozilla").build(), discarding()); 218 | 219 | httpClientMock.verify().get("/login").withHeader("User-Agent", "Mozilla").called(); 220 | httpClientMock.verify().get("/login").withHeader("User-Agent", "Chrome").called(); 221 | httpClientMock.verify().get("/login").withHeader("User-Agent", "IE").notCalled(); 222 | } 223 | 224 | @Test 225 | void should_verify_each_part_of_URL_in_separate() throws Exception { 226 | final HttpClientMock httpClientMock = new HttpClientMock(); 227 | httpClientMock.onGet("http://localhost:8080/login?foo=bar#ref").doReturn("OK"); 228 | httpClientMock.send(TestRequests.get("http://localhost:8080/login?foo=bar#ref"), discarding()); 229 | 230 | httpClientMock.verify().get().withHost("localhost").called(); 231 | httpClientMock.verify().get().withHost("google").notCalled(); 232 | httpClientMock.verify().get().withPath("/login").called(); 233 | httpClientMock.verify().get().withPath("/logout").notCalled(); 234 | httpClientMock.verify().get().withParameter("foo","bar").called(); 235 | httpClientMock.verify().get().withParameter("foo","hoo").notCalled(); 236 | httpClientMock.verify().get().withReference("ref").called(); 237 | httpClientMock.verify().get().withReference("fer").notCalled(); 238 | 239 | } 240 | 241 | @Test 242 | void should_verify_custom_condition() throws Exception { 243 | final HttpClientMock httpClientMock = new HttpClientMock(); 244 | httpClientMock.onGet("http://localhost:8080/login?foo=bar#ref").doReturn("OK"); 245 | httpClientMock.send(TestRequests.get("http://localhost:8080/login?foo=bar#ref"), discarding()); 246 | 247 | httpClientMock.verify().get().with(request -> request.uri().getFragment().length()==3).called(); 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/HttpResponseMatchers.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import org.hamcrest.BaseMatcher; 4 | import org.hamcrest.Description; 5 | import org.hamcrest.Matcher; 6 | 7 | import java.net.http.HttpResponse; 8 | 9 | public final class HttpResponseMatchers { 10 | 11 | public static Matcher hasStatus(int expectedStatus) { 12 | return new BaseMatcher<>() { 13 | @Override 14 | public boolean matches(Object o) { 15 | if (!(o instanceof HttpResponse)) { 16 | return false; 17 | } 18 | return ((HttpResponse) o).statusCode() == expectedStatus; 19 | } 20 | 21 | @Override 22 | public void describeTo(Description description) { 23 | description.appendValue(expectedStatus); 24 | } 25 | }; 26 | } 27 | 28 | public static Matcher hasContent(final String content) { 29 | return new BaseMatcher<>() { 30 | @SuppressWarnings("unchecked") 31 | @Override 32 | public boolean matches(Object o) { 33 | if (!(o instanceof HttpResponse)) { 34 | return false; 35 | } 36 | 37 | final HttpResponse response = (HttpResponse) o; 38 | return response.body() != null && response.body().equals(content); 39 | } 40 | 41 | @Override 42 | public void describeTo(Description description) { 43 | description.appendText(content); 44 | } 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/TestRequests.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import java.net.URI; 4 | import java.net.http.HttpRequest; 5 | 6 | import static java.net.http.HttpRequest.BodyPublishers.noBody; 7 | import static java.net.http.HttpRequest.newBuilder; 8 | 9 | class TestRequests { 10 | 11 | static public HttpRequest post(String url) { 12 | return newBuilder(URI.create(url)).POST(noBody()).build(); 13 | } 14 | 15 | static public HttpRequest get(String url) { 16 | return newBuilder(URI.create(url)).GET().build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/UrlConditionsTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient; 2 | 3 | import com.pgssoft.httpclient.internal.UrlConditions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | class UrlConditionsTest { 9 | 10 | private static final int EMPTY_PORT_NUMBER = -1; 11 | 12 | @Test 13 | void parseHost() { 14 | UrlConditions urlConditions = UrlConditions.parse("http://localhost"); 15 | assertTrue(urlConditions.getSchemaCondition().matches("http")); 16 | assertTrue(urlConditions.getHostCondition().matches("localhost")); 17 | assertTrue(urlConditions.getPortCondition().matches(EMPTY_PORT_NUMBER)); 18 | assertTrue(urlConditions.getReferenceCondition().matches("")); 19 | } 20 | 21 | 22 | @Test 23 | void parseHostWithPort() { 24 | UrlConditions urlConditions = UrlConditions.parse("http://localhost:8080"); 25 | assertTrue(urlConditions.getHostCondition().matches("localhost")); 26 | assertTrue(urlConditions.getPortCondition().matches(8080)); 27 | } 28 | 29 | @Test 30 | void parseHostAndPath() { 31 | UrlConditions urlConditions = UrlConditions.parse("http://localhost/foo/bar"); 32 | assertTrue(urlConditions.getHostCondition().matches("localhost")); 33 | assertTrue(urlConditions.getPathCondition().matches("/foo/bar")); 34 | } 35 | 36 | @Test 37 | void parseHostAndPathAndParameters() { 38 | UrlConditions urlConditions = UrlConditions.parse("http://localhost/foo/bar?a=1&b=2"); 39 | assertTrue(urlConditions.getHostCondition().matches("localhost")); 40 | assertTrue(urlConditions.getPathCondition().matches("/foo/bar")); 41 | assertTrue(urlConditions.getParameterConditions().matches("a=1&b=2")); 42 | assertTrue(urlConditions.getParameterConditions().matches("b=2&a=1")); 43 | } 44 | 45 | @Test 46 | void parseHostReference() { 47 | UrlConditions urlConditions = UrlConditions.parse("http://localhost#abc"); 48 | assertTrue(urlConditions.getHostCondition().matches("localhost")); 49 | assertTrue(urlConditions.getReferenceCondition().matches("abc")); 50 | } 51 | 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/test/java/com/pgssoft/httpclient/internal/UrlParamsTest.java: -------------------------------------------------------------------------------- 1 | package com.pgssoft.httpclient.internal; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class UrlParamsTest { 10 | 11 | @Test 12 | void shouldParseQueryString() { 13 | UrlParams urlParams = UrlParams.parse("a=1&b=2"); 14 | List params = urlParams.getParams(); 15 | assertEquals("a", params.get(0).getName()); 16 | assertEquals("1", params.get(0).getValues().get(0)); 17 | assertEquals("b", params.get(1).getName()); 18 | assertEquals("2", params.get(1).getValues().get(0)); 19 | assertTrue(urlParams.contains("a")); 20 | assertTrue(urlParams.contains("b")); 21 | assertFalse(urlParams.contains("c")); 22 | } 23 | 24 | @Test 25 | void shouldReturnEmptyListForNull() { 26 | assertEquals(0, UrlParams.parse(null).getParams().size()); 27 | } 28 | 29 | } 30 | --------------------------------------------------------------------------------