- Print these instructions!
- Run
./prep-demo
script, which will delete the contract tests and initialise the podman container environment. It also clears the database if the architecture recorder is running.
- Pause any backup software
- Turn on Mac Focus
- Quit email and other messaging tools
- Make terminal and IDE fonts huge
- Make browser font huge
- Reduce screen resolution to 1920 x 1080
- Sort out web conference green screen if necessary
- Get an iPad with a timer running
Open three IDEs, one for carpet-shopper
, weaver
, and wookie-tamer
.
Open a terminal within each IDE (or three OS terminals).
- If this is the first time you are running this, build/install the
observer extension
:cd observer-extension ./mvnw install
- Start the architecture recorder:
cd architecture-recorder quarkus dev
It may be useful to clear all architecture information, or just the historical interactions.
curl -i -X POST http://localhost:8088/recorder/clearall
OR
curl -i -X POST http://localhost:8088/recorder/clearinteractions
- Start the
carpet-shopper
service withquarkus dev --clean
. - Visit http://localhost:8080. The app has a React front end and a Quarkus back end, stitched together and bridged by Quarkus Quinoa.
- Try and do an order. Nothing will happen; there are no other services.
- Start the
weaver
service (quarkus dev --clean
). - Try and do an order. Nothing will happen; we need Wookie fur.
- Start the
wookie-tamer
service (wookie-tamer/start-wookie-tamer-with-pact.sh
). - Do an order for a brown carpet. It should succeed, and an order should appear.
- We've had to do quite a lot of starting of services, just to see if our app works... and this is a trivial application. Microservices are hard!
- Show the tests. Because of course there are tests, we're responsible developers. The tests should have also automatically been running in the background of each app via continuous testing.
- Run the back-end tests in IntelliJ
- Refactor the
Skein
record in thewookie-tamer
app.- Colour is a British spelling. To refactor,
shift-f6
oncolour
variable, change it to ‘color’. Getters will update too.
- Colour is a British spelling. To refactor,
- Sense check. Run the Java tests (all working), all working in all services (& continuous testing should have picked up changes as well & re-tested).
- Visit the web page again. It's all going to work, right, because the tests all worked?
- Shouldn't the unit tests have caught this? Look at
FurResourceTest
.- Because we're using the object model, our IDE automatically refactored
colour()
tocolor()
. - We could have done more hard-coding in the tests to do json-path type expressions, but that's kind of icky, and the IDE might have refactored the hard-coded strings, too.
- If we were to lock the hard-coded strings and say they can't be changed ... well, that's basically a contract. But it's only on one side, with no linkage to the other side, so it's pretty manual and error-prone.
- Because we're using the object model, our IDE automatically refactored
- How can we fix this? The app is broken, but the tests are all green. This is where contract tests give that extra validation to allow us to confirm assumptions.
- Pact is consumer-driven contract testing, so we start with the consumer. It's a bit like a TDD principle, start with the expectations.
- Open the
weaver
project in a terminal (or switch to it if you already opened it). - In a terminal, run
quarkus extension add quarkus-pact-consumer
- Add the tests
-
If you're in a hurry, use the git history to recreate the contract tests. Rollback any
pom.xml
andCarpetResourceContractTest
changes from the git history. -
Otherwise, copy the
CarpetResourceTest
and use it as the starting point.- The test method stays exactly the same, because we're trying to validate the behaviour of our weaver code.
-
The mocking logic is a bit different.
CarpetResourceTest
is mocking the entire call to thewookie-tamer
. Instead, we want it to actually make a call to the Pact mock server.- Delete the mock injection of the
WookieService
and theBeforeEach
method (setUp()
) - pact-tab for the following live template:
@Pact(consumer = "weaver") public V4Pact requestingFurContract(PactDslWithProvider builder) { var headers = Map.of(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); // Here we define our mock, which is also our expectations for the provider // This defines what the body of the request could look like // we are generic and say it can be anything that meets the schema var furOrderBody = newJsonBody(body -> body .stringType("colour") .numberType("orderNumber") ).build(); // And then define what the response from the mock should look like, which becomes part of the contract var furBody = newJsonBody(body -> body.stringValue("colour", "brown")).build(); return builder .uponReceiving("A request for wookie fur") .path("/fur/order") .headers(headers) .method(HttpMethod.POST) .body(furOrderBody) .willRespondWith() .status(Status.OK.getStatusCode()) .headers(headers) .body(furBody) .toPact(V4Pact.class); }
- Delete the mock injection of the
-
- Finally, we need to add some extra annotations. extend-tab on the class declaration to add
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "wookie-tamer", port = "8096")
- Show the
CarpetResourceTest
and then compare the two tests.- Explain the differences are because Pact acts both as a mock and a validator of all possible values.
- Restart the tests. A json contract has appeared in
weaver/target/pacts
. - The test should pass, we're the consumer, we made assumptions about how the provider should behave. But are those assumptions correct? Now is when we find out!
- Publish the Pact contract so it is available to the
wookie-tamer
service via 1 of the following 2 mechanisms:- If you are NOT using the Pact broker, run
weaver/publish-contracts.sh
.- Normally this would be done by automatically checking it into source control or by using a pact broker.
- If you ARE using the Pact broker, run
weaver/publish-contracts-to-broker.sh
.
- If you are NOT using the Pact broker, run
- Restore the
FurResourceContractVerificationTests
test from history. - Add the pact provider dependency by running
quarkus extension add quarkus-pact-provider
(or by restoringwookie-tamer/pom.xml
from history). - Run the Java tests, show the failure, explain how it could be fixed by negotiating a contract change or changing the source code.
-
We were too vague in our contract. We actually said in the contract any colour would give a brown carpet.
- In
CarpetResourceContractTest
, in therequestingFurContract
contract method, change
var furOrderBody = newJsonBody(body -> body .stringType("colour") .numberType("orderNumber") ).build();
to
var furOrderBody = newJsonBody(body -> body .stringValue("colour", "pink") .numberType("orderNumber") ).build();
- In
The provider contract tests in wookie-tamer
should still pass, but now the consumer tests in weaver
are failing.
(Normally we would build up the tests, but to keep it simple, we will just change the test.)
- Now publish the tests, and we have the failure.
- If you do want to add both tests, you can copy the existing pact and test methods, and change the colours in the copy. You will also need to add
@PactTestFor(pactMethod = "requestingPinkFurContract")
onto the test method. (By default, Pact will only stand up the first @Pact
for the right provider.)
- So we have a failing test, but what's the right fix? Fallback to brown isn't right, there should be some kind of error.
- We think we should have a
418
, not a brown carpet.418
isI'm a teapot
, maybe not the right code, but it's my code, so I can return what I want. Also, it keeps behaviour of the different services distinct. - Look at
NotFoundExceptionHandler
, which turnsNotFoundException
s into418
s. - Update the tests to expect a
418
.@Test public void testCarpetEndpointForPinkCarpet() { var order = new CarpetOrder("pink", 16); given() .contentType(ContentType.JSON) .body(order) .when() .post("/carpet/order") .then() .statusCode(418); }
- Update the implementation in
CarpetResource
to wrap the invocation in atry
and} catch (Exception e) { throw new NotFoundException(order.getColour()); }
- The tests should pass. All good, except ...
- ... publish the tests, and they fail on the other side.
- Update the code to return
null
instead of brown as the fallback, and ... they should still fail. - In this case the right thing to do is to update the contract to return
204
, and update implementation to instead to anull
check. The implementation would be to add aif (skein == null) {
check in CarpetResource
and then throw an exception from the null
check body.