wiremock: Verify request with duplicate query params

I’m really enjoying using wiremock (2.0.10-beta) and have hit a problem trying to verify a call with duplicate query parameters:

/authz/access/12345?operations=op1&operations=op2

My naive attempt didn’t work:

        wiremock.verify(getRequestedFor(urlPathEqualTo("/authz/access/12345"))
                .withQueryParam("operations", equalTo("op1"))
                .withQueryParam("operations", equalTo("op2"))
                .withHeader("Authorization", equalTo("Bearer goodtoken")));

The wiremock logs show that the second withQueryParam is overwriting the first one.

com.github.tomakehurst.wiremock.client.VerificationException: Expected at least one request matching: {
  "urlPath" : "/authz/access/12345",
  "method" : "GET",
  "headers" : {
    "Authorization" : {
      "equalTo" : "Bearer goodtoken"
    }
  },
  "queryParameters" : {
    "operations" : {
      "equalTo" : "op2"
    }
  }
}

Is this a type of verification that Wiremock would consider supporting?

In the meantime, I’m just using the following workaround (and hoping for consistent ordering):

        wiremock.verify(getRequestedFor(urlEqualTo("/authz/access/12345?operations=op1&operations=op2"))
                .withHeader("Authorization", equalTo("Bearer goodtoken")));

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 23
  • Comments: 32 (9 by maintainers)

Most upvoted comments

We’ve just come up against this again and ended up back on this issue! Any progress yet? 😄

+1 having the same issue

Sorry, still not managed to get this over the line 😞

BTW, you can now combine custom and standard matchers so you could work around this by writing a custom matcher just for the query part.

Here’s one way you can do this with hamcrest matchers to verify (contains) or ignore (containsInAnyOrder) the order of the duplicated parameters. RID_STRINGS is just a list of strings representing the expected parameter values.

        wireMockRule.stubFor(get(urlPathEqualTo("/endpoint"))
                .withQueryParam("userId", equalTo(USER_ID.toString()))
                .andMatching(request ->
                        MatchResult.of(
                                Matchers.containsInAnyOrder(RID_STRINGS.toArray(String[]::new))
                                        .matches(request.queryParameter("rids").values())
                        ))
        .willReturn(okJson(responseJson)));

Any update? 😦

Here’s my suggested design for this:

stubFor(get(urlPathEqualTo("/things"))
                .withQueryParam("id", havingExactly("1", "2", "3")) // There must be 3 values of id exactly whose values are 1, 2, and 3
      .willReturn(ok()));

stubFor(get(urlPathEqualTo("/things"))
      .withQueryParam("id", havingExactly( // There must be 3 values of id exactly whose values conform to the match expressions
              equalTo("1"),
              containing("2"),
              notContaining("3")
      )).willReturn(ok()));

stubFor(get(urlPathEqualTo("/things"))
      .withQueryParam("id", including("1", "2", "3")) // The values of id must include 1, 2, and 3
      .willReturn(ok()));

stubFor(get(urlPathEqualTo("/things"))
      .withQueryParam("id", including( // The values of id must include those specified by the matches
              equalTo("1"),
              containing("2"),
              notContaining("3")
      )).willReturn(ok()));

JSON will look something like:

{
  "mapping": {
    "request" : {
      "urlPath" : "/things",
      "method" : "GET",
      "queryParameters" : {
        "id" : {
          "hasExactly" : [
            {
              "equalTo": "1"
            },
            {
              "contains": "2"
            },
            {
              "doesNotContain": "3"
            }
          ]
        }
      }
    },
    "response" : {
      "status" : 200
    }
  }
}
{
  "mapping": {
    "request" : {
      "urlPath" : "/things",
      "method" : "GET",
      "queryParameters" : {
        "id" : {
          "includes" : [
            {
              "equalTo": "1"
            },
            {
              "contains": "2"
            },
            {
              "doesNotContain": "3"
            }
          ]
        }
      }
    },
    "response" : {
      "status" : 200
    }
  }
}

No update?😦

Sorry, still not managed to get this over the line 😞

BTW, you can now combine custom and standard matchers so you could work around this by writing a custom matcher just for the query part.

@tomakehurst cool, got it then probably we can go via way you suggested. You can assign this issue to me.

The intention is that the parameters are varargs in all cases, I just chose 3 as an example.

contains, equalTo etc. are the existing matchers and these new ones will make use of them.

Ordering should not matter so the first example would match a query like ?id=1&id=2&id=3 and ?id=2&id=3&id=1.

In kotlin, an extension function like this got me the desired behaviour (variation of @forrestrice answer):

private fun MappingBuilder.withQueryParamsList(key: String, values: List<String>) =
    andMatching {
        MatchResult.of(values.toSet() == it.queryParameter(key).values().toSet())
    }.let { this }

usage is similar to existing functions:

stubFor(
  get(urlPathEqualTo(urlPath))
    .withQueryParamsList("ids", listOf("1", "2"))
    .withQueryParam("from", EqualToDateTimePattern("2023-02-27T14:35:16.853225Z"))
    .willReturn(aResponse())
)

matchingAll can be confusing, my initial expectation for:

.withHeader("X-My-Header", matchingAll(
    equalTo("1"),
    containing("2"),
    matching("th.*"))
)

was that one X-My-Header has to match all the patterns.

How about adding a new method:

.withRepeatingHeader("X-My-Header", matchingInAnyOrder( // or matchingExactly
    equalTo("1"),
    containing("2"),
    matching("th.*"))
)

matchingInAnyOrder will match any valid permutation of the matchers while matchingExactly will need the matchers to match in the exact order they appear.

I’m working on it. Keeping the API backwards compatible is proving a bit tricky.

Here’s a test case showing what I’m aiming at: https://github.com/tomakehurst/wiremock/blob/multi-value-aggregate-matching/src/test/java/com/github/tomakehurst/wiremock/MultiValuedParameterAcceptanceTest.java

I am also having this problem. It is a really a useful feature. Was it implemented in a branch or something? If not, I’d love to work on this.

Thanks! Just to confirm, I wanted to verify that both operations=op1 AND operations=op2 query params appear in one http call.