All Downloads are FREE. Search and download functionalities are using the official Maven repository.

feign.mock.MockClient Maven / Gradle / Ivy

/*
 * Copyright 2012-2023 The Feign Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package feign.mock;

import static feign.Util.UTF_8;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import feign.*;
import feign.Request.ProtocolVersion;

public class MockClient implements Client, AsyncClient {

  static class RequestResponse {

    private final RequestKey requestKey;

    private final Response.Builder responseBuilder;

    public RequestResponse(RequestKey requestKey, Response.Builder responseBuilder) {
      this.requestKey = requestKey;
      this.responseBuilder = responseBuilder;
    }

  }

  private final List responses = new ArrayList<>();

  private final Map> requests = new HashMap<>();

  private boolean sequential;

  private Iterator responseIterator;

  public MockClient() {}

  public MockClient(boolean sequential) {
    this.sequential = sequential;
  }

  @Override
  public synchronized Response execute(Request request, Request.Options options)
      throws IOException {
    RequestKey requestKey = RequestKey.create(request);
    Response.Builder responseBuilder;
    if (sequential) {
      responseBuilder = executeSequential(requestKey);
    } else {
      responseBuilder = executeAny(request, requestKey);
    }
    responseBuilder.protocolVersion(ProtocolVersion.MOCK);

    return responseBuilder.request(request).build();
  }

  @Override
  public CompletableFuture execute(Request request,
                                             Request.Options options,
                                             Optional requestContext) {
    RequestKey requestKey = RequestKey.create(request);
    Response.Builder responseBuilder;
    if (sequential) {
      responseBuilder = executeSequential(requestKey);
    } else {
      responseBuilder = executeAny(request, requestKey);
    }
    responseBuilder.protocolVersion(ProtocolVersion.MOCK);

    return CompletableFuture.completedFuture(responseBuilder.request(request).build());
  }

  private Response.Builder executeSequential(RequestKey requestKey) {
    Response.Builder responseBuilder;
    if (responseIterator == null) {
      responseIterator = responses.iterator();
    }
    if (!responseIterator.hasNext()) {
      throw new VerificationAssertionError("Received excessive request %s", requestKey);
    }

    RequestResponse expectedRequestResponse = responseIterator.next();
    if (!expectedRequestResponse.requestKey.equalsExtended(requestKey)) {
      throw new VerificationAssertionError("Expected: \n%s,\nbut was: \n%s",
          expectedRequestResponse.requestKey,
          requestKey);
    }

    responseBuilder = expectedRequestResponse.responseBuilder;
    return responseBuilder;
  }

  private Response.Builder executeAny(Request request, RequestKey requestKey) {
    Response.Builder responseBuilder;
    if (requests.containsKey(requestKey)) {
      requests.get(requestKey).add(request);
    } else {
      requests.put(requestKey, new ArrayList<>(Arrays.asList(request)));
    }

    responseBuilder = getResponseBuilder(request, requestKey);
    return responseBuilder;
  }

  private Response.Builder getResponseBuilder(Request request, RequestKey requestKey) {
    Response.Builder responseBuilder = null;
    for (RequestResponse requestResponse : responses) {
      if (requestResponse.requestKey.equalsExtended(requestKey)) {
        responseBuilder = requestResponse.responseBuilder;
        // Don't break here, last one should win to be compatible with
        // previous
        // releases of this library!
      }
    }
    if (responseBuilder == null) {
      responseBuilder =
          Response.builder().status(HttpURLConnection.HTTP_NOT_FOUND).reason("Not mocker")
              .headers(request.headers());
    }
    return responseBuilder;
  }

  public MockClient ok(HttpMethod method, String url, InputStream responseBody) throws IOException {
    return ok(RequestKey.builder(method, url).build(), responseBody);
  }

  public MockClient ok(HttpMethod method, String url, String responseBody) {
    return ok(RequestKey.builder(method, url).build(), responseBody);
  }

  public MockClient ok(HttpMethod method, String url, byte[] responseBody) {
    return ok(RequestKey.builder(method, url).build(), responseBody);
  }

  public MockClient ok(HttpMethod method, String url) {
    return ok(RequestKey.builder(method, url).build());
  }

  public MockClient ok(RequestKey requestKey, InputStream responseBody) throws IOException {
    return ok(requestKey, Util.toByteArray(responseBody));
  }

  public MockClient ok(RequestKey requestKey, String responseBody) {
    return ok(requestKey, responseBody.getBytes(UTF_8));
  }

  public MockClient ok(RequestKey requestKey, byte[] responseBody) {
    return add(requestKey, HttpURLConnection.HTTP_OK, responseBody);
  }

  public MockClient ok(RequestKey requestKey) {
    return ok(requestKey, (byte[]) null);
  }

  public MockClient add(HttpMethod method, String url, int status, InputStream responseBody)
      throws IOException {
    return add(RequestKey.builder(method, url).build(), status, responseBody);
  }

  public MockClient add(HttpMethod method, String url, int status, String responseBody) {
    return add(RequestKey.builder(method, url).build(), status, responseBody);
  }

  public MockClient add(HttpMethod method, String url, int status, byte[] responseBody) {
    return add(RequestKey.builder(method, url).build(), status, responseBody);
  }

  public MockClient add(HttpMethod method, String url, int status) {
    return add(RequestKey.builder(method, url).build(), status);
  }

  /**
   * @param response
   *        
    *
  • the status defaults to 0, not 200!
  • *
  • the internal feign-code requires the headers to be set
  • *
*/ public MockClient add(HttpMethod method, String url, Response.Builder response) { return add(RequestKey.builder(method, url).build(), response); } public MockClient add(RequestKey requestKey, int status, InputStream responseBody) throws IOException { return add(requestKey, status, Util.toByteArray(responseBody)); } public MockClient add(RequestKey requestKey, int status, String responseBody) { return add(requestKey, status, responseBody.getBytes(UTF_8)); } public MockClient add(RequestKey requestKey, int status, byte[] responseBody) { return add(requestKey, Response.builder().status(status).reason("Mocked").headers(RequestHeaders.EMPTY) .body(responseBody)); } public MockClient add(RequestKey requestKey, int status) { return add(requestKey, status, (byte[]) null); } public MockClient add(RequestKey requestKey, Response.Builder response) { responses.add(new RequestResponse(requestKey, response)); return this; } /** * @deprecated use {@link #add(HttpMethod, String, feign.Response.Builder)} instead */ @Deprecated public MockClient add(HttpMethod method, String url, Response response) { return this.add(method, url, response.toBuilder()); } public MockClient noContent(HttpMethod method, String url) { return add(method, url, HttpURLConnection.HTTP_NO_CONTENT); } public Request verifyOne(HttpMethod method, String url) { return verifyTimes(method, url, 1).get(0); } public Request verifyOne(RequestKey requestKey) { return verifyTimes(requestKey, 1).get(0); } public List verifyTimes(final HttpMethod method, final String url, final int times) { if (times < 0) { throw new IllegalArgumentException("times must be a non negative number"); } if (times == 0) { verifyNever(method, url); return Collections.emptyList(); } RequestKey requestKey = RequestKey.builder(method, url).build(); if (!requests.containsKey(requestKey)) { throw new VerificationAssertionError("Wanted: '%s' but never invoked! Got: %s", requestKey, requests.keySet()); } List result = requests.get(requestKey); if (result.size() != times) { throw new VerificationAssertionError("Wanted: '%s' to be invoked: '%s' times but got: '%s'!", requestKey, times, result.size()); } return result; } public List verifyTimes(RequestKey requestKey, final int times) { if (times < 0) { throw new IllegalArgumentException("times must be a non negative number"); } if (times == 0) { verifyNever(requestKey); return Collections.emptyList(); } List result = null; for (Map.Entry> request : requests.entrySet()) { if (request.getKey().equalsExtended(requestKey)) { result = request.getValue(); } } if (result == null) { throw new VerificationAssertionError("Wanted: '%s' but never invoked! Got: %s", requestKey, requests.keySet()); } if (result.size() != times) { throw new VerificationAssertionError("Wanted: '%s' to be invoked: '%s' times but got: '%s'!", requestKey, times, result.size()); } return result; } public void verifyNever(HttpMethod method, String url) { RequestKey requestKey = RequestKey.builder(method, url).build(); if (requests.containsKey(requestKey)) { throw new VerificationAssertionError("Do not wanted: '%s' but was invoked!", requestKey); } } public void verifyNever(RequestKey requestKey) { for (RequestKey recorderRequestKey : requests.keySet()) { if (recorderRequestKey.equalsExtended(requestKey)) { throw new VerificationAssertionError("Do not wanted: '%s' but was invoked!", requestKey); } } } /** * To be called in an @After method: * *
   * @After
   * public void tearDown() {
   *   mockClient.verifyStatus();
   * }
   * 
*/ public void verifyStatus() { if (sequential) { boolean unopenedIterator = responseIterator == null && !responses.isEmpty(); if (unopenedIterator || responseIterator.hasNext()) { throw new VerificationAssertionError("More executions were expected"); } } } public void resetRequests() { requests.clear(); } }