mock-server / mockserver

MockServer enables easy mocking of any system you integrate with via HTTP or HTTPS with clients written in Java, JavaScript and Ruby. MockServer also includes a proxy that introspects all proxied traffic including encrypted SSL traffic and supports Port Forwarding, Web Proxying (i.e. HTTP proxy), HTTPS Tunneling Proxying (using HTTP CONNECT) and SOCKS Proxying (i.e. dynamic port forwarding).
http://mock-server.com
Apache License 2.0
4.52k stars 1.06k forks source link

Multiple responses for the same request are not thread-safe #1834

Open dzianis-kryvapusk opened 5 months ago

dzianis-kryvapusk commented 5 months ago

Describe the issue I noticed that MockServerClient responds with only the first mocked response even if it has multiple of them when an app calls mocked API concurrently many times.

What you are trying to do An app calls third-party API many times in a non-blocking manner. It means that MockServerClient gets many requests at the same time. This third-party API should respond with a different response every time. I mock them but receive only the first mocked response. When I added a delay between calls, everything began to work as expected.

MockServer version org.mock-server:mockserver-spring-test-listener:5.15.0

To Reproduce I prepared a project with a test (test_getJokes_successful) which you can run and see the issue. You will find that the response from the server contains only the first mocked response repeated many times. That's why assertEquals(expected, response) will fail. To fix it, you need to uncomment lines in the method com.example.jokemicroservice.jokeprovider.ThirdPartyJokeProvider#getJokesAsync. You can run this test directly from your IDE. Expectations are built by the following code:

for (int i = 1; i <= 5; i++) {
            JokeDto joke = buildJoke(i);
            expected.add(joke);
            mockServer.when(request()
                                    .withMethod(HttpMethod.GET.name())
                                    .withPath(jokeApiProperties.getPath()),
                            once())
                    .respond(response()
                                     .withStatusCode(HttpStatusCode.OK_200.code())
                                     .withContentType(org.mockserver.model.MediaType.APPLICATION_JSON)
                                     .withBody(objectMapper.writeValueAsString(joke)));
        }

Expected behaviour The server should respond with a mocked response only once even on concurrent calls.

MockServer Log In the attached file you can find many logs like

matched expectation:

  {
    "httpRequest" : {
      "method" : "GET",
      "path" : "/random_joke"
    },
    "httpResponse" : {
      "statusCode" : 200,
      "headers" : {
        "content-type" : [ "application/json" ]
      },
      "body" : "{\"id\":1,\"type\":null,\"setup\":\"test setup 1\",\"punchline\":\"test punchline 1\"}"
    },
    "id" : "b5a90ca1-5dce-4c9e-9b6a-95d11bf92a13",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "remainingTimes" : 1
    }
  }

which lead to the negative remainingTimes

2024-01-24T12:29:37.462+04:00  INFO 10420 --- [erver-EventLog0] org.mockserver.log.MockServerEventLog    : 1080 removed expectation:

  {
    "httpRequest" : {
      "method" : "GET",
      "path" : "/random_joke"
    },
    "httpResponse" : {
      "statusCode" : 200,
      "headers" : {
        "content-type" : [ "application/json" ]
      },
      "body" : "{\"id\":1,\"type\":null,\"setup\":\"test setup 1\",\"punchline\":\"test punchline 1\"}"
    },
    "id" : "b5a90ca1-5dce-4c9e-9b6a-95d11bf92a13",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "remainingTimes" : -2
    }
  }

 with id:

  b5a90ca1-5dce-4c9e-9b6a-95d11bf92a13

mock-server.log