Hey folks, another question on how to write a good...
# general
s
Hey folks, another question on how to write a good contract test. We have a consumer that makes a call to retrieve a list of objects; it then iterates over the list of objects to find one with a matching name and proceeds to do whatever parsing/validation necessary on that particular object in the list. I realize this might not be the best interaction model to express in pact, and it would've been better to just call GET to retrieve that particular object, but it is what it is, unfortunately. I can talk to the service team to eventually change it. Still, I was wondering what would be the right way to model this in pact. Basically the contract is sorta: • return a list of objects of the same type • the list must contain one and only one object with name X, as described in the provider state • it can have more elements or just that one, consumer does not care And then we probably want to separately test the case when the consumer receives a list that does not contain the given element.
b
Is the name passed as a parameter in the request? Or is it a latent coupling that the contract couldn't definitely fulfil?
You can still put contract tests around it, expressible with Pact, and they might give you more confidence in the edge cases, e.g. • make provider states that have 0, 1, and >1 matching names • write consumer tests that describe what happens in each state • You can use the
like
matchers (
atLeastOneLike
and others in JS, idk what language your consumer is in) to try to constrain the logic, but it's a bit hard to put strong reasoning around that kind of interaction.
Ideally, fix the API up in future, so the client doesn't have to do the work itself (e.g. an endpoint that passes the name as a param, and gets back the expected entity, or a list if that makes more sense)
(or a smaller list response that just has names & IDs, then you GET the one you want by ID)
s
thanks! > Is the name passed as a parameter in the request? Or is it a latent coupling that the contract couldn't definitely fulfil? It is a latent coupling, unfortunately. How would my pact be different if it was explicit in the request? > You can use the
like
matchers (
atLeastOneLike
and others in JS, idk what language your consumer is in) to try to constrain the logic, but it's a bit hard to put strong reasoning around that kind of interaction. so how would I express something like
a list of items, each has a name field, and for one of them the name equals X, and that item matches the template, don't care about the others
? If I do something like:
Copy code
EachLike(Map{"name": Equality("x")})
this means the provider can respond with a list that contains multiple items with the same name? Or should my consumer not care? > Ideally, fix the API up in future, so the client doesn't have to do the work itself (e.g. an endpoint that passes the name as a param, and gets back the expected entity, or a list if that makes more sense) agreed, this would be ideal, but idk if/when the api team can get to this
b
I don't think you can say "exactly one item matches this criteria, and no others do" with the Pact DSL
s
gotcha. Well, I don't think it matters much since I think the consumer code will pick the first one that matches anyway.
💡 1
b
How would my pact be different if it was explicit in the request?
If an expected value in the response is supplied in the request, it's easier to demonstrate the coupling. Worst case, though, it can be coupled in the state description.
s
ah, so it's not about that pact can somehow match against the term in the request, but more like to make the pact more readable?
b
It's both. The contract describes the request and the response as a pair, so you can explicitly say that the request has a value that will be echoed somewhere in the response, e.g.
Copy code
GET /lol/face?name=very-important-value
expects a response like
Copy code
{
  "faces": [
    {
      "id": int(example: 3),
      "name": "very-important-value"
    }
  ]
}
s
isn't it the same with the provider state though? If we say
Given("very-important-value exists")
and expect the same response as above, isn't it the same?
b
it's very close, yes . . . not sure if it's exactly the same
I think it depends what version of state descriptors you use. A newer feature lets you supply multiple data values in state descriptions, and the name could be one.
🧠 1
💡 1
s
I wonder if this is a good way to describe the contract:
Copy code
{
  "faces": [
    {
      "id": int(example: 3),
      "name": "very-important-value"
    },
    {
      "id": int(example: 5),
       "name": string(example: 'whatever')
    }
  ]
}
with state like
given(very-important-value exists, among others)
(but structured)
👀 1
b
Further: if it's just in the state name as a string, you don't have isolated access to it without parsing the string. If you put it in a param that's already parsed (e.g. path element, query param, or body value) then you can reuse it in the providers state implementation.
👌 1
s
and have, as you suggested above, multiple test cases - with one, zero or multiple results
b
Yep, I think that's the starting point. Then you can champion the changes you want with these examples as supporting evidence 😎
👍 1
s
yup, that's the plan. Tests like this can help make a case for API improvements
party parrot 1
Thanks for your help!
b
np, gl