Ulises Cervino
01/30/2023, 2:29 PMBoris
01/31/2023, 1:26 AMBoris
01/31/2023, 1:26 AMUlises Cervino
01/31/2023, 8:00 AMBoris
01/31/2023, 9:50 AMBoris
01/31/2023, 9:51 AMBoris
01/31/2023, 9:51 AMBoris
01/31/2023, 9:53 AMUlises Cervino
01/31/2023, 9:53 AMpathFromProvider("/entity/${id}", "/entity/1")
Ulises Cervino
01/31/2023, 9:54 AMbody.stringMatcher("id", "1")
(using lambdadsl)Ulises Cervino
01/31/2023, 9:54 AMUlises Cervino
01/31/2023, 9:55 AM"id" -> 100
and have the pact be verified no problemUlises Cervino
01/31/2023, 9:55 AMBoris
01/31/2023, 9:56 AMbody.stringValue
or body.string
.Ulises Cervino
01/31/2023, 9:56 AMUlises Cervino
01/31/2023, 9:56 AMstringValue
, but then there was no matchers in the pact.json, which was a bit confusingUlises Cervino
01/31/2023, 9:56 AMBoris
01/31/2023, 9:57 AMUlises Cervino
01/31/2023, 10:00 AMBoris
01/31/2023, 10:01 AMBoris
01/31/2023, 10:02 AMUlises Cervino
01/31/2023, 11:12 AMUlises Cervino
02/01/2023, 10:26 AMUlises Cervino
02/01/2023, 10:27 AMUlises Cervino
02/01/2023, 10:30 AMConsumer:
---------
@Pact(consumer = "C", provider = "P")
RequestResponsePact getExistingLiveBasket(PactDslWithProvider builder) {
return builder.given("a LIVE basket exists")
.uponReceiving("retrieve LIVE basket")
.headers(REQUEST_HEADERS)
.method("GET")
.pathFromProviderState("/${basketId}", "/" + existingBasketId)
.willRespondWith()
.status(200)
.headers(RESPONSE_HEADERS)
.body(LambdaDsl.newJsonBody(
body ->
body.stringValue("basketId", existingBasketId.toString())
.stringMatcher("status", "LIVE"))
.build())
.toPact();
}
@Test
@PactTestFor(pactMethod = "getExistingLiveBasket", pactVersion = PactSpecVersion.V3)
void createBasket_whenServiceIsRunningFine(MockServer server) throws JsonProcessingException {
ResponseBasket responseBasket =
RestAssured.given()
.headers(REQUEST_HEADERS)
.when()
.get(String.format("%s/%s", server.getUrl(), existingBasketId))
.then()
.statusCode(200)
.extract()
.as(ResponseBasket.class);
assertEquals(existingBasketId, responseBasket.getBasketId());
assertEquals(ResponseBasket.StatusEnum.LIVE, responseBasket.getStatus());
}
Ulises Cervino
02/01/2023, 10:31 AMUlises Cervino
02/01/2023, 10:31 AMUlises Cervino
02/01/2023, 10:31 AM@State("a LIVE basket exists")
Map liveBasketExists(Map _ignored_params) {
Map<String, Object> state = new HashMap<>();
InternalBasket internalBasket =
repository.createBasket(TestSupport.Repository.basketWithDefaults()).orElseThrow();
// the key in this state-map corresponds to the variable in the expression in the consumer test, i.e. in the
// example consumer test we have a call like .pathFromProviderState("/${basketId}", ...)
state.put("basketId", internalBasket.getBasketId());
return state;
}
Ulises Cervino
02/01/2023, 10:32 AMUlises Cervino
02/01/2023, 10:32 AMMatt (pactflow.io / pact-js / pact-go)
when I get entity with ID 1 I will confirm that the response has the same IDI’m not sure this is an important thing to capture in a contract test. This smells more like a functional test to me
Boris
02/02/2023, 4:30 AMdescribe('can get a specific experiment', () => {
// given:
beforeEach(() =>
provider.addInteraction({
state: 'a view and two samplers with experiments',
uponReceiving: 'request to show experiment 0 in sampler 1 in view 0',
withRequest: {
method: 'GET',
path: '/view/0/sampler/1/experiment/0',
headers: headers.request
},
willRespondWith: {
status: 200,
headers: headers.response,
body: {
name: like('Some experiment'),
id: '0',
metrics: eachLike({ type: 'event', name: 'view' }, { min: 1 }),
variants: eachLike({ name: 'jim', data: '' }, { min: 2 }),
state: like('unscheduled')
}
}
})
);
// when:
it('', () =>
client.experiment
.get(0, 1, 0)
// then:
.then(body => {
expect(body).to.eql({
name: 'Some experiment',
id: '0',
metrics: [{ type: 'event', name: 'view' }],
variants: [{ name: 'jim', data: '' }, { name: 'jim', data: '' }],
state: 'unscheduled'
});
})
.catch(fail));
});
Boris
02/02/2023, 4:32 AM{
"description": "request to show experiment 0 in sampler 1 in view 0",
"providerState": "a view and two samplers with experiments",
"request": {
"method": "GET",
"path": "/view/0/sampler/1/experiment/0",
"headers": {
"Accept": "application/json"
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"name": {
"json_class": "Pact::SomethingLike",
"contents": "Some experiment"
},
"id": "0",
"metrics": {
"json_class": "Pact::ArrayLike",
"contents": {
"type": "event",
"name": "view"
},
"min": 1
},
"variants": {
"json_class": "Pact::ArrayLike",
"contents": {
"name": "jim",
"data": ""
},
"min": 2
},
"state": {
"json_class": "Pact::SomethingLike",
"contents": "unscheduled"
}
}
}
},
Boris
02/02/2023, 4:32 AMBoris
02/02/2023, 4:37 AM"gets an existing biller" {
val pact = builder
.given("a cached biller list")
.uponReceiving("a request for biller details").run {
headers(authHeaders)
method(GET)
path("/bpaybiller/24208")
}
.willRespondWith().run {
headers(contentTypeHeaders)
status(200)
body(LambdaDsl.newJsonBody { with(it) {
stringValue("code", "24208")
stringType("name", "SOUTH EAST WATER")
stringType("longName", "SOUTH EAST WATER CORPORATION")
} }.build())
}
.toPact()
verify(pact) {
fetchBiller(
GetBillerDetailsRequest(code = "24208")
) shouldBe Response.Success(
BpayBiller(
code = "24208",
shortName = "SOUTH EAST WATER",
longName = "SOUTH EAST WATER CORPORATION"
)
)
}
}
This one is also over 5 years old, and while Kotlin, is using the same Java8 Lambda DSL, I think.Boris
02/02/2023, 4:37 AMbody.stringValue("code", "24208")
worked for me all the way back then. If it's not working now, there might be a bug.Matt (pactflow.io / pact-js / pact-go)
Ulises Cervino
02/02/2023, 8:53 AMbody.stringValue(...)
nothing comes out in the pact file, furthermore we're using pathFromProviderState
. You maybe have a point about a check like "has the same ID as the path I queried" being a non-quite-a-contract-test thing. We can certainly add other sorts of tests that cover this scenario, however I got curious and I suspect many/most our consumers will come up with that question.Boris
02/02/2023, 9:03 AMstringValue
doesn't make changes in the Pactfile, it's a bug. It'd be great if you could raise a bug on Github :)Ulises Cervino
02/02/2023, 10:31 AMUlises Cervino
02/03/2023, 8:48 AMstringValue
, but I did look into valueFromProviderState
which is the way of telling the provider, via the contract: this field will have a value that you need to fill in.Ulises Cervino
02/03/2023, 2:06 PM"matchingRules": {
"body": {
"$.basketId": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
which I'm unsure is the right thingUlises Cervino
02/03/2023, 2:06 PMbody.valueFromProviderState(
"basketId",
"basketId",
existingBasketId.toString())
Ulises Cervino
02/03/2023, 2:06 PMtoString
bit then it's null
all the way through)Ulises Cervino
02/03/2023, 2:06 PMMatt (pactflow.io / pact-js / pact-go)
generators
sectionuglyog
pathFromProviderState
for the path, and then valueFromProviderState
in the body, with both using the same expression, then there will be two generator entries added to the Pact file that will replace the values with the result from the provider state callback.Ulises Cervino
02/06/2023, 8:28 AM${varName}
in the path, and then varName
in the value, it doesn't do what I was expecting it to do (in my provider state I return a map containing varName -> actualValue
). My expectation, after reading the docs for valueFromProviderState
was that if you just specify a name like varName
in the expression parameter, it'd use that as key, but that didn't quite work for me. Having them both use ${varName}
works just fine though, which is nice.Ulises Cervino
02/06/2023, 8:29 AMvalueFromProvider('name', '$varName', uuidObject)
then the generator is of type RAW
(which is fine in principle) however the matchers try to match with null
, if instead I provide uuidObject.toString()
then the generator is of type String
(good stuff) and then the values need to matchUlises Cervino
02/06/2023, 8:30 AMuglyog
another gotcha was that if I do valueFromProvider('name', '$varName', uuidObject) then the generator is of type RAW (which is fine in principle) however the matchers try to match with null, if instead I provide uuidObject.toString() then the generator is of type String (good stuff) and then the values need to matchThat sounds like a bug,
uuidObject
some type of object?Boris
02/06/2023, 10:31 PMUlises Cervino
02/07/2023, 7:59 AMUUID
class, yes. I don't mind raw (doing Object
sort of equals) but I do mind it being checked against null
Ulises Cervino
02/07/2023, 8:00 AMtoString
is a workaround though