Skip to content

Commit 8a1307d

Browse files
committed
Upgrade demo to microcks-testcontainers 0.3.0 features
Signed-off-by: Laurent Broudoux <laurent.broudoux@gmail.com>
1 parent ca62a3e commit 8a1307d

File tree

7 files changed

+243
-15
lines changed

7 files changed

+243
-15
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
<dependency>
6262
<groupId>io.github.microcks</groupId>
6363
<artifactId>microcks-testcontainers</artifactId>
64-
<version>0.2.10</version>
64+
<version>0.3.0</version>
6565
<scope>test</scope>
6666
</dependency>
6767
<dependency>

src/test/java/org/acme/order/api/OrderControllerContractTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package org.acme.order.api;
22

3+
import io.github.microcks.testcontainers.model.RequestResponsePair;
34
import io.github.microcks.testcontainers.model.TestRequest;
45
import io.github.microcks.testcontainers.model.TestResult;
56
import io.github.microcks.testcontainers.model.TestRunnerType;
67

78
import com.fasterxml.jackson.annotation.JsonInclude;
9+
import com.fasterxml.jackson.core.type.TypeReference;
810
import com.fasterxml.jackson.databind.ObjectMapper;
911
import org.acme.order.BaseIntegrationTest;
1012
import org.junit.jupiter.api.Test;
1113

14+
import java.util.List;
15+
import java.util.Map;
16+
1217
import static org.junit.jupiter.api.Assertions.assertEquals;
1318
import static org.junit.jupiter.api.Assertions.assertTrue;
1419

@@ -32,4 +37,40 @@ void testOpenAPIContract() throws Exception {
3237
assertTrue(testResult.isSuccess());
3338
assertEquals(1, testResult.getTestCaseResults().size());
3439
}
40+
41+
@Test
42+
void testOpenAPIContractAndBusinessConformance() throws Exception {
43+
// Ask for an Open API conformance to be launched.
44+
TestRequest testRequest = new TestRequest.Builder()
45+
.serviceId("Order Service API:0.1.0")
46+
.runnerType(TestRunnerType.OPEN_API_SCHEMA.name())
47+
.testEndpoint("http://host.testcontainers.internal:" + port + "/api")
48+
.build();
49+
50+
TestResult testResult = microcksEnsemble.getMicrocksContainer().testEndpoint(testRequest);
51+
52+
// You may inspect complete response object with following:
53+
ObjectMapper mapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
54+
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(testResult));
55+
56+
assertTrue(testResult.isSuccess());
57+
assertEquals(1, testResult.getTestCaseResults().size());
58+
59+
// You may also check business conformance.
60+
List<RequestResponsePair> pairs = microcksEnsemble.getMicrocksContainer().getMessagesForTestCase(testResult, "POST /orders");
61+
for (RequestResponsePair pair : pairs) {
62+
if ("201".equals(pair.getResponse().getStatus())) {
63+
Map<String, Object> requestMap = mapper.readValue(pair.getRequest().getContent(), new TypeReference<>() {});
64+
Map<String, Object> responseMap = mapper.readValue(pair.getResponse().getContent(), new TypeReference<>() {});
65+
66+
List<Map<String, Object>> requestPQ = (List<Map<String, Object>>) requestMap.get("productQuantities");
67+
List<Map<String, Object>> responsePQ = (List<Map<String, Object>>) responseMap.get("productQuantities");
68+
69+
assertEquals(requestPQ.size(), responsePQ.size());
70+
for (int i = 0; i < requestPQ.size(); i++) {
71+
assertEquals(requestPQ.get(i).get("productName"), responsePQ.get(i).get("productName"));
72+
}
73+
}
74+
}
75+
}
3576
}

src/test/java/org/acme/order/client/PastryAPIClientTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.List;
99

1010
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
1112

1213
class PastryAPIClientTests extends BaseIntegrationTest {
1314

@@ -25,10 +26,17 @@ void testGetPastries() {
2526

2627
pastries = client.listPastries("L");
2728
assertEquals(2, pastries.size());
29+
30+
// Check that the mock API has really been invoked.
31+
boolean mockInvoked = microcksEnsemble.getMicrocksContainer().verify("API Pastries", "0.0.1");
32+
assertTrue(mockInvoked, "Mock API not invoked");
2833
}
2934

3035
@Test
3136
void testGetPastry() {
37+
// Get the number of invocations before our test.
38+
long beforeMockInvocations = microcksEnsemble.getMicrocksContainer().getServiceInvocationsCount("API Pastries", "0.0.1");
39+
3240
// Test our API client and check that arguments and responses are correctly serialized.
3341
Pastry pastry = client.getPastry("Millefeuille");
3442
assertEquals("Millefeuille", pastry.name());
@@ -41,5 +49,9 @@ void testGetPastry() {
4149
pastry = client.getPastry("Eclair Chocolat");
4250
assertEquals("Eclair Chocolat", pastry.name());
4351
assertEquals("unknown", pastry.status());
52+
53+
// Check our mock API has been invoked the correct number of times.
54+
long afterMockInvocations = microcksEnsemble.getMicrocksContainer().getServiceInvocationsCount("API Pastries", "0.0.1");
55+
assertEquals(3, afterMockInvocations - beforeMockInvocations, "Mock API not invoked the correct number of times");
4456
}
4557
}

src/test/java/org/acme/order/service/OrderServiceTests.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package org.acme.order.service;
22

3+
import io.github.microcks.testcontainers.model.EventMessage;
34
import io.github.microcks.testcontainers.model.TestRequest;
45
import io.github.microcks.testcontainers.model.TestResult;
56
import io.github.microcks.testcontainers.model.TestRunnerType;
7+
import io.github.microcks.testcontainers.model.UnidirectionalEvent;
68

9+
import com.fasterxml.jackson.core.type.TypeReference;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
711
import org.acme.order.BaseIntegrationTest;
812
import org.acme.order.service.model.Order;
913
import org.acme.order.service.model.OrderInfo;
@@ -17,6 +21,7 @@
1721

1822
import java.time.Duration;
1923
import java.util.List;
24+
import java.util.Map;
2025
import java.util.Properties;
2126
import java.util.concurrent.CompletableFuture;
2227
import java.util.concurrent.TimeUnit;
@@ -69,7 +74,23 @@ void testEventIsPublishedWhenOrderIsCreated() {
6974
assertFalse(testResult.getTestCaseResults().isEmpty());
7075
assertEquals(1, testResult.getTestCaseResults().get(0).getTestStepResults().size());
7176

72-
System.err.println(microcksEnsemble.getAsyncMinionContainer().getLogs());
77+
//System.err.println(microcksEnsemble.getAsyncMinionContainer().getLogs());
78+
79+
// Check the content of the emitted event, read from Kafka topic.
80+
List<UnidirectionalEvent> events = microcksEnsemble.getMicrocksContainer()
81+
.getEventMessagesForTestCase(testResult, "SUBSCRIBE orders-created");
82+
83+
assertEquals(1, events.size());
84+
85+
EventMessage message = events.get(0).getEventMessage();
86+
Map<String, Object> messageMap = new ObjectMapper().readValue(message.getContent(), new TypeReference<>() {});
87+
88+
// Properties from the event message should match the order.
89+
assertEquals("Creation", messageMap.get("changeReason"));
90+
Map<String, Object> orderMap = (Map<String, Object>) messageMap.get("order");
91+
assertEquals("123-456-789", orderMap.get("customerId"));
92+
assertEquals(8.4, orderMap.get("totalPrice"));
93+
assertEquals(2, ((List<?>) orderMap.get("productQuantities")).size());
7394
} catch (Exception e) {
7495
fail("No exception should be thrown when testing Kafka publication", e);
7596
}

step-1-getting-started.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,32 @@ You need to have a [Docker](https://docs.docker.com/get-docker/) or [Podman](htt
1919
$ docker version
2020

2121
Client:
22-
Cloud integration: v1.0.35+desktop.10
23-
Version: 25.0.3
24-
API version: 1.44
25-
Go version: go1.21.6
26-
Git commit: 4debf41
27-
Built: Tue Feb 6 21:13:26 2024
22+
Version: 27.3.1
23+
API version: 1.47
24+
Go version: go1.22.7
25+
Git commit: ce12230
26+
Built: Fri Sep 20 11:38:18 2024
2827
OS/Arch: darwin/arm64
2928
Context: desktop-linux
3029

31-
Server: Docker Desktop 4.27.2 (137060)
30+
Server: Docker Desktop 4.36.0 (175267)
3231
Engine:
33-
Version: 25.0.3
34-
API version: 1.44 (minimum version 1.24)
35-
Go version: go1.21.6
36-
Git commit: f417435e5f6216828dec57958c490c4f8bae4f98
37-
Built: Wed Feb 7 00:39:16 2024
32+
Version: 27.3.1
33+
API version: 1.47 (minimum version 1.24)
34+
Go version: go1.22.7
35+
Git commit: 41ca978
36+
Built: Fri Sep 20 11:41:19 2024
3837
OS/Arch: linux/arm64
3938
Experimental: false
39+
containerd:
40+
Version: 1.7.21
41+
GitCommit: 472731909fa34bd7bc9c087e4c27943f9835f111
42+
runc:
43+
Version: 1.1.13
44+
GitCommit: v1.1.13-0-g58aa920
45+
docker-init:
46+
Version: 0.19.0
47+
GitCommit: de40ad0
4048
```
4149

4250
## Download the project

step-4-write-rest-tests.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,62 @@ sequenceDiagram
109109
PastryAPIClient-->>-PastryAPIClientTests: List<Pastry>
110110
```
111111

112+
### Bonus step - Check the mock endpoints are actually used
113+
114+
While the above test is a good start, it doesn't actually check that the mock endpoints are being used. In a more complex application, it's possible
115+
that the client is not correctly configured or use some cache or other mechanism that would bypass the mock endpoints. In order to check that you
116+
can actually use the `verify()` method available on the Microcks container:
117+
118+
```java
119+
@Test
120+
public void testGetPastries() {
121+
// Test our API client and check that arguments and responses are correctly serialized.
122+
List<Pastry> pastries = client.listPastries("S");
123+
assertEquals(1, pastries.size());
124+
125+
pastries = client.listPastries("M");
126+
assertEquals(2, pastries.size());
127+
128+
pastries = client.listPastries("L");
129+
assertEquals(2, pastries.size());
130+
131+
// Check that the mock API has really been invoked.
132+
boolean mockInvoked = microcksEnsemble.getMicrocksContainer().verify("API Pastries", "0.0.1");
133+
assertTrue(mockInvoked, "Mock API not invoked");
134+
}
135+
```
136+
137+
`verify()` takes the target API name and version as arguments and returns a boolean indicating if the mock has been invoked. This is a good way to
138+
ensure that the mock endpoints are actually being used in your test.
139+
140+
If you need finer-grained control, you can also check the number of invocations with `getServiceInvocationsCount()`. This way you can check that
141+
the mock has been invoked the correct number of times:
142+
143+
```java
144+
@Test
145+
void testGetPastry() {
146+
// Get the number of invocations before our test.
147+
long beforeMockInvocations = microcksEnsemble.getMicrocksContainer().getServiceInvocationsCount("API Pastries", "0.0.1");
148+
149+
// Test our API client and check that arguments and responses are correctly serialized.
150+
Pastry pastry = client.getPastry("Millefeuille");
151+
assertEquals("Millefeuille", pastry.name());
152+
assertEquals("available", pastry.status());
153+
154+
pastry = client.getPastry("Eclair Cafe");
155+
assertEquals("Eclair Cafe", pastry.name());
156+
assertEquals("available", pastry.status());
157+
158+
pastry = client.getPastry("Eclair Chocolat");
159+
assertEquals("Eclair Chocolat", pastry.name());
160+
assertEquals("unknown", pastry.status());
161+
162+
// Check our mock API has been invoked the correct number of times.
163+
long afterMockInvocations = microcksEnsemble.getMicrocksContainer().getServiceInvocationsCount("API Pastries", "0.0.1");
164+
assertEquals(3, afterMockInvocations - beforeMockInvocations, "Mock API not invoked the correct number of times");
165+
}
166+
```
167+
112168
## Second Test - Verify the technical conformance of Order Service API
113169

114170
The 2nd thing we want to validate is the conformance of the `Order API` we'll expose to consumers. In this section and the next one,
@@ -245,6 +301,53 @@ reuses Postman collection constraints.
245301
You're now sure that beyond the technical conformance, the `Order Service` also behaves as expected regarding business
246302
constraints.
247303

304+
### Bonus step - Verify the business conformance of Order Service API in pure Java
305+
306+
Even if the Postman Collection runner is a great way to validate business conformance, you may want to do it in pure Java.
307+
This is possible by retrieving the messages exchanged during the test and checking their content. Let's review the `testOpenAPIContractAndBusinessConformance()`
308+
test in class `OrderControllerContractTests` under `src/test/java/org/acme/order/api`:
309+
310+
```java
311+
@Test
312+
void testOpenAPIContractAndBusinessConformance() throws Exception {
313+
// Ask for an Open API conformance to be launched.
314+
TestRequest testRequest = new TestRequest.Builder()
315+
.serviceId("Order Service API:0.1.0")
316+
.runnerType(TestRunnerType.OPEN_API_SCHEMA.name())
317+
.testEndpoint("http://host.testcontainers.internal:" + port + "/api")
318+
.build();
319+
320+
TestResult testResult = microcksEnsemble.getMicrocksContainer().testEndpoint(testRequest);
321+
322+
assertTrue(testResult.isSuccess());
323+
assertEquals(1, testResult.getTestCaseResults().size());
324+
325+
// You may also check business conformance.
326+
List<RequestResponsePair> pairs = microcksEnsemble.getMicrocksContainer().getMessagesForTestCase(testResult, "POST /orders");
327+
for (RequestResponsePair pair : pairs) {
328+
if ("201".equals(pair.getResponse().getStatus())) {
329+
Map<String, Object> requestMap = mapper.readValue(pair.getRequest().getContent(), new TypeReference<>() {});
330+
Map<String, Object> responseMap = mapper.readValue(pair.getResponse().getContent(), new TypeReference<>() {});
331+
332+
List<Map<String, Object>> requestPQ = (List<Map<String, Object>>) requestMap.get("productQuantities");
333+
List<Map<String, Object>> responsePQ = (List<Map<String, Object>>) responseMap.get("productQuantities");
334+
335+
assertEquals(requestPQ.size(), responsePQ.size());
336+
for (int i = 0; i < requestPQ.size(); i++) {
337+
assertEquals(requestPQ.get(i).get("productName"), responsePQ.get(i).get("productName"));
338+
}
339+
}
340+
}
341+
}
342+
```
343+
344+
This test is a bit more complex than the previous ones. It first asks for an OpenAPI conformance test to be launched and then retrieves the messages
345+
to check business conformance, following the same logic that was implemented into the Postman Collection snippet.
346+
347+
It uses the `getMessagesForTestCase()` method to retrieve the messages exchanged during the test and then checks the content. While this is done
348+
in pure Java here, you may use the tool or library of your choice like [JSONAssert](https://github.com/skyscreamer/JSONassert),
349+
[Cucumber](https://cucumber.io/docs/installation/java/) or others
350+
248351

249352
###
250353
[Next](step-5-write-async-tests.md)

step-5-write-async-tests.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Things are a bit more complex here, but we'll walk through step-by-step:
8686

8787
The sequence diagram below details the test sequence. You'll see 2 parallel blocks being executed:
8888
* One that corresponds to Microcks test - where it connects and listen for Kafka messages,
89-
* One that corresponds to the `OrderService` invokation that is expected to trigger a message on Kafka.
89+
* One that corresponds to the `OrderService` invocation that is expected to trigger a message on Kafka.
9090

9191
```mermaid
9292
sequenceDiagram
@@ -113,6 +113,49 @@ Because the test is a success, it means that Microcks has received an `OrderEven
113113
conformance with the AsyncAPI contract or this event-driven architecture. So you're sure that all your Spring Boot configuration, Kafka JSON serializer
114114
configuration and network communication are actually correct!
115115

116+
### Bonus step - Verify the event content
117+
118+
So you're now sure that an event has been sent to Kafka and that it's valid regarding the AsyncAPI contract. But what about the content
119+
of this event? If you want to go further and check the content of the event, you can do it by asking Microcks the events read during the
120+
test execution and actually check their content. This can be done adding a few lines of code:
121+
122+
```java
123+
@Test
124+
void testEventIsPublishedWhenOrderIsCreated() {
125+
// [...] Unchanged comparing previous step.
126+
127+
try {
128+
// [...] Unchanged comparing previous step.
129+
130+
// Get the Microcks test result.
131+
TestResult testResult = testRequestFuture.get();
132+
133+
// [...] Unchanged comparing previous step.
134+
135+
// Check the content of the emitted event, read from Kafka topic.
136+
List<UnidirectionalEvent> events = microcksEnsemble.getMicrocksContainer()
137+
.getEventMessagesForTestCase(testResult, "SUBSCRIBE orders-created");
138+
139+
assertEquals(1, events.size());
140+
141+
EventMessage message = events.get(0).getEventMessage();
142+
Map<String, Object> messageMap = new ObjectMapper().readValue(message.getContent(), new TypeReference<>() {});
143+
144+
// Properties from the event message should match the order.
145+
assertEquals("Creation", messageMap.get("changeReason"));
146+
Map<String, Object> orderMap = (Map<String, Object>) messageMap.get("order");
147+
assertEquals("123-456-789", orderMap.get("customerId"));
148+
assertEquals(8.4, orderMap.get("totalPrice"));
149+
assertEquals(2, ((List<?>) orderMap.get("productQuantities")).size());
150+
} catch (Exception e) {
151+
fail("No exception should be thrown when testing Kafka publication", e);
152+
}
153+
}
154+
```
155+
156+
Here, we're using the `getEventMessagesForTestCase()` method on the Microcks container to retrieve the messages read during the test execution.
157+
Using the wrapped `EventMessage` class, we can then check the content of the message and assert that it matches the order we've created.
158+
116159
## Second Test - Verify our OrderEventListener is processing events
117160

118161
In this section, we'll focus on testing the `Event Consumer` + `Order Service` components of our application:

0 commit comments

Comments
 (0)