MockClient.java
/**
* The MIT License
*
* Copyright for portions of unirest-java are held by Kong Inc (c) 2013.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package kong.unirest.core;
import java.net.http.WebSocket;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.function.Supplier;
import static java.util.concurrent.CompletableFuture.completedFuture;
/**
* A Mock client for unirest to make requests against
* This implements both sync and async clients
*/
public class MockClient implements Client {
private final Supplier<Config> config;
private List<Routes> routes = new ArrayList<>();
private SocketSet remoteSocket;
private Invocation defaultResponse;
public MockClient(Supplier<Config> config){
this.config = config;
}
/**
* Creates a new MockClient and registers it on the primary static UnirestInstance
* @return the Mock Client
*/
public static MockClient register() {
return register(Unirest.primaryInstance());
}
/**
* Creates a new MockClient and registers it on the Unirest instance
* @param unirest an instance of Unirest
* @return the Mock Client
*/
public static MockClient register(UnirestInstance unirest) {
MockClient client = new MockClient(unirest::config);
unirest.config().httpClient(client);
return client;
}
/**
* Clears any MockClient from the primary instance
*/
public static void clear() {
clear(Unirest.primaryInstance());
}
/**
* Clears any MockClient from the instance
* @param unirest the instance to clear the mocks from
*/
public static void clear(UnirestInstance unirest) {
if(unirest.config().getClient() instanceof MockClient){
unirest.config().httpClient((Client) null);
}
}
@Override
public <T> HttpResponse<T> request(HttpRequest request, Function<RawResponse, HttpResponse<T>> transformer, Class<?> resultType) {
Routes exp = findExpecation(request);
Config c = this.config.get();
c.getUniInterceptor().onRequest(request, c);
MetricContext metric = c.getMetric().begin(request.toSummary());
RawResponse response = exp.exchange(request, c);
metric.complete(new ResponseSummary(response), null);
HttpResponse<T> rez = transformer.apply(response);
c.getUniInterceptor().onResponse(rez, request.toSummary(), c);
return rez;
}
private Routes findExpecation(HttpRequest request) {
return routes.stream()
.filter(e -> e.matches(request))
.findFirst()
.orElseGet(() -> createNewPath(request));
}
private Routes createNewPath(HttpRequest request) {
Routes p = new Routes(request, defaultResponse);
routes.add(p);
return p;
}
@Override
public <T> CompletableFuture<HttpResponse<T>> request(HttpRequest request,
Function<RawResponse, HttpResponse<T>> transformer,
CompletableFuture<HttpResponse<T>> callback,
Class<?> resultTypes) {
return CompletableFuture.supplyAsync(() -> request(request, transformer, resultTypes));
}
@Override
public WebSocketResponse websocket(WebSocketRequest request, WebSocket.Listener listener) {
MockWebSocket clientSocket = new MockWebSocket();
WebSocket.Listener clientListener = listener;
MockWebSocket serverWebSocket = new MockWebSocket();
MockListener serverListener = new MockListener();
remoteSocket = new SocketSet(serverWebSocket, serverListener, "server");
clientSocket.init(remoteSocket);
serverWebSocket.init(new SocketSet(clientSocket, clientListener, "client"));
return new WebSocketResponse(completedFuture(clientSocket), clientListener);
}
public SocketSet<MockWebSocket, MockListener> serversSocket() {
if(remoteSocket == null){
throw new UnirestException("No Socket Yet Established");
}
return remoteSocket;
}
@Override
public Object getClient() {
return this;
}
/**
* Start an expectation chain.
* @param method the Http method
* @param path the base path
* @return an Expectation which can have additional criteria added to it.
*/
public Expectation expect(HttpMethod method, String path) {
Path p = new Path(path);
Routes exp = findByPath(method, p).orElseGet(() -> new Routes(method, p));
if(!this.routes.contains(exp)) {
this.routes.add(exp);
}
return exp.newExpectation();
}
/**
* Expect ANY call to a path with this method
* @param method the Http Method
* @return this expectation builder
*/
public Expectation expect(HttpMethod method) {
return expect(method, null);
}
/**
* Assert a specific method and path were invoked
* @param method the Http method
* @param path the base path
* @return an Assert object which can have additional criteria chained to it.
*/
public Assert assertThat(HttpMethod method, String path) {
return findByPath(method, new Path(path))
.orElseThrow(() -> new UnirestAssertion(String.format("No Matching Invocation:: %s %s", method, path)));
}
private Optional<Routes> findByPath(HttpMethod get, Path path) {
return routes.stream()
.filter(e -> e.matches(get, path))
.findFirst();
}
/**
* Verify that all Expectations were invoked
*/
public void verifyAll() {
routes.forEach(Routes::verifyAll);
}
/**
* Reset all expectations
*/
public void reset() {
routes.clear();
defaultResponse = null;
}
/**
* return this status for any request that doesn't match a expectation
*/
public ExpectedResponse defaultResponse() {
this.defaultResponse = new Invocation();
return this.defaultResponse.thenReturn();
}
}