Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 3541

How to Mock Multiple Responses for the Same Request

$
0
0

1. Overview

In this article, we’ll explore how to mock multiple responses for the same request using MockServer.

A MockServer simulates real APIs by mimicking their behavior, allowing us to test applications without needing backend services.

2. Application Set Up

Let’s consider a payment processing API that provides an endpoint for handling payment requests. When a payment is initiated, this API calls an external bank payment service. The bank’s API responds with a reference paymentId. Using this ID, the API periodically checks the payment status by polling the bank’s API, ensuring the payment is processed successfully.

Let’s begin by defining the payment request model, which includes the card details needed to process the payment:

public record PaymentGatewayRequest(
  String cardNumber, String expiryMonth, String expiryYear, String currency, int amount, String cvv) {
}

Similarly, let’s define the payment response model, which contains the payment status:

public record PaymentGatewayResponse(UUID id, PaymentStatus status) {
    public enum PaymentStatus {
        PENDING,
        AUTHORIZED,
        DECLINED,
        REJECTED
    }
}

Now, let’s add the controller and implementation to integrate with the bank’s payment service for submitting payment and status polling. The API will keep polling while the payment status starts as pending and later updates to AUTHORIZED, DECLINED, or REJECTED:

@PostMapping("payment/process")
public ResponseEntity<PaymentGatewayResponse> submitPayment(@RequestBody PaymentGatewayRequest paymentGatewayRequest) 
  throws JSONException {
    String paymentSubmissionResponse = webClient.post()
      .uri("http://localhost:9090/payment/submit")
      .body(BodyInserters.fromValue(paymentGatewayRequest))
      .retrieve()
      .bodyToMono(String.class)
      .block();
    UUID paymentId = UUID.fromString(new JSONObject(paymentSubmissionResponse).getString("paymentId"));
    PaymentGatewayResponse.PaymentStatus paymentStatus = PaymentGatewayResponse.PaymentStatus.PENDING;
    while (paymentStatus.equals(PaymentGatewayResponse.PaymentStatus.PENDING)) {
        String paymentStatusResponse = webClient.get()
          .uri("http://localhost:9090/payment/status/%s".formatted(paymentId))
          .retrieve()
          .bodyToMono(String.class)
          .block();
        paymentStatus = PaymentGatewayResponse.PaymentStatus.
          valueOf(new JSONObject(paymentStatusResponse).getString("paymentStatus"));
        logger.info("Payment Status {}", paymentStatus);
    }
    return new ResponseEntity<>(new PaymentGatewayResponse(paymentId, paymentStatus), HttpStatus.OK);
}

To test this API and ensure it polls the payment status until reaching a terminal state, we need the ability to mock multiple responses from the payment status API. The mock response should initially return a PENDING status a few times before updating to AUTHORIZED, enabling us to effectively validate the polling mechanism.

3. How to Mock Multiple Responses for the Same Requests

The first step in testing this API is to start a mock server on port 9090. Our API uses this port to interact with the bank’s payment submission and status services:

class PaymentControllerTest {
    private ClientAndServer clientAndServer;
    private final MockServerClient mockServerClient = new MockServerClient("localhost", 9090);
    @BeforeEach
    void setup() {
        clientAndServer = startClientAndServer(9090);
    }
    
    @AfterEach
    void tearDown() {
        clientAndServer.stop();
    }
    // ...
}

Next, let’s set up a mock for the payment submission endpoint to return the paymentId:

mockServerClient
  .when(request()
    .withMethod("POST")
    .withPath("/payment/submit"))
  .respond(response()
    .withStatusCode(200)
    .withBody("{\"paymentId\": \"%s\"}".formatted(paymentId))
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));

To mock multiple responses for the same request, we need to use the Times class together with the when() method.

The when() method uses the Times argument to specify how many times a request should match. This allows us to mock different responses for repeated requests.

Following that, let’s mock the payment status endpoint to return a PENDING status 4 times:

mockServerClient
  .when(request()
    .withMethod("GET")
    .withPath("/payment/status/%s".formatted(paymentId)), Times.exactly(4))
  .respond(response()
    .withStatusCode(200)
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .withBody("{\"paymentStatus\": \"%s\"}"
    .formatted(PaymentGatewayResponse.PaymentStatus.PENDING.toString())));

Next, let’s mock the payment status endpoint to return AUTHORIZED:

mockServerClient
  .when(request()
    .withMethod("GET")
    .withPath("/payment/status/%s".formatted(paymentId)))
  .respond(response()
    .withStatusCode(200)
    .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .withBody("{\"paymentStatus\": \"%s\"}"
    .formatted(PaymentGatewayResponse.PaymentStatus.AUTHORIZED.toString())));

Lastly, let’s send a request to the payment processing API endpoint to receive the AUTHORIZED result:

webTestClient.post()
  .uri("http://localhost:9000/api/payment/process")
  .bodyValue(new PaymentGatewayRequest("4111111111111111", "12", "2025", "USD", 10000, "123"))
  .exchange()
  .expectStatus()
  .isOk()
  .expectBody(PaymentGatewayResponse.class)
  .value(response -> {
      Assertions.assertNotNull(response);
      Assertions.assertEquals(PaymentGatewayResponse.PaymentStatus.AUTHORIZED, response.status());
  });

We should see the log printing “Payment Status PENDING” four times, followed by “Payment Status AUTHORIZED“.

4. Conclusion

In this tutorial, we explored how to mock multiple responses for the same request, enabling flexible testing of APIs using the Times class.

The default when() method in MockServerClient uses Times.unlimited() to respond to all matching requests consistently. To mock a response for a specific number of requests, we can use Times.exactly().

As always, the source code for the examples is available over on GitHub.

       

Viewing all articles
Browse latest Browse all 3541

Trending Articles