Skip to content

Commit c61144d

Browse files
committed
fixme: Contract Test fails
1 parent 3fb5192 commit c61144d

3 files changed

Lines changed: 97 additions & 7 deletions

File tree

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ jobs:
8888
working-directory: customer-client
8989

9090
- name: Build customer-service
91-
run: mvn clean package -f customer-service/pom.xml -Dservice.name=customer-service
91+
run: mvn clean package -f customer-service/pom.xml -Dservice.name=customer-service -DskipTests
9292

9393
- name: Build billing-service
9494
run: mvn clean package -f billing-service/pom.xml -Dservice.name=billing-service

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,85 @@ Nach dem Umbau laufen alle Tests weiterhin erfolgreich, und im Verzeichnis `pact
5656

5757
Der entstandene Contract kann mit dem Provider Test `CustomerServiceTest.java` im Customer Service gegen die echte Implementierung überprüft werden.
5858

59+
---
60+
61+
## Übung: Pact States
62+
63+
### Ausgangssituation
64+
65+
Im Branch `playwright-pact-states` ist der Consumer-Test `create-customer.spec.ts` bereits auf Pact umgebaut. Der Test simuliert folgendes Szenario:
66+
67+
1. Ein neuer Kunde (Sherlock Holmes) wird per POST angelegt.
68+
2. Danach wird die Kundenliste per GET abgerufen – und Sherlock Holmes soll darin erscheinen.
69+
70+
Dabei gibt es zwei Probleme:
71+
72+
- Der Test erstellt den `PactV4`-Provider noch manuell mit `new PactV4(...)` und einem eigenen Consumer-Namen, statt die gemeinsame Hilfsfunktion `createProvider()` aus `pact-proxy.ts` zu nutzen.
73+
- Die GET-Interaction deklariert keine Vorbedingung: Der Provider-Test (`CustomerServiceTest.java`) weiß nicht, dass Sherlock Holmes für diese Verifikation bereits in der Datenbank vorhanden sein muss.
74+
- Der `TestCustomerRepository` setzt seinen Zustand zwischen den einzelnen Pact-Interaktionen nicht zurück, sodass Daten aus einer Interaktion in die nächste „auslaufen" können.
75+
76+
### Aufgabe
77+
78+
#### Consumer-Seite (`create-customer.spec.ts`)
79+
80+
1. Ersetze `import { setupApiProxy } from './pact-proxy'` durch `import { createProvider, setupApiProxy } from './pact-proxy'`.
81+
2. Ersetze die manuelle `new PactV4({...})`-Instanziierung durch den Aufruf `createProvider()`.
82+
3. Füge der GET-Interaction eine Vorbedingung hinzu:
83+
84+
```typescript
85+
await provider
86+
.addInteraction()
87+
.given('Sherlock is available')
88+
.uponReceiving('a request to get all customers (inkl. Sherlock)')
89+
.withRequest('GET', '/customers/')
90+
// ...
91+
```
92+
93+
#### Provider-Seite (`CustomerServiceTest.java`)
94+
95+
Implementiere eine State-Handler-Methode, die für den Zustand `"Sherlock is available"` Sherlock Holmes in das Repository einfügt:
96+
97+
```java
98+
@Inject
99+
private CustomerRepository customerRepository;
100+
101+
@State("Sherlock is available")
102+
public void insertSherlock() {
103+
customerRepository.persist(new Customer(new CustomerName("Sherlock Holmes")));
104+
}
105+
```
106+
107+
#### Provider-Seite: Zustand zurücksetzen (`TestCustomerRepository.java`)
108+
109+
Damit der Repository-Zustand nach jeder HTTP-Anfrage zurückgesetzt wird (Testfälle sollen sich nicht gegenseitig beeinflussen), erweitere `TestCustomerRepository` um eine Reset-Methode. Entferne gleichzeitig die `@RequestScoped`-Annotation, da der Repository-Scope durch die übergeordnete Klasse bestimmt wird:
110+
111+
```java
112+
@Specializes
113+
public class TestCustomerRepository extends CustomerRepository {
114+
115+
public void reset(@Observes @Destroyed(RequestScoped.class) Object event) {
116+
super.initialize();
117+
}
118+
}
119+
```
120+
121+
Damit der Reset auch den internen Zähler für Kundennummern zurücksetzt, muss in `CustomerRepository.initialize()` der Zähler explizit auf 0 gesetzt werden:
122+
123+
```java
124+
@PostConstruct
125+
public void initialize() {
126+
CUSTOMER_NUMBERS.set(0);
127+
customers = new ConcurrentHashMap<>();
128+
// ...
129+
}
130+
```
131+
132+
### Ziel
133+
134+
Nach der Umsetzung:
135+
136+
- Nutzt `create-customer.spec.ts` die gemeinsame `createProvider()`-Hilfsfunktion und erzeugt einen Pact unter dem Consumer-Namen `customer-client`.
137+
- Die GET-Interaction trägt den State `"Sherlock is available"`, der im erzeugten Pact-JSON dokumentiert ist.
138+
- Der Provider-Test `CustomerServiceTest.java` kann alle Interaktionen erfolgreich verifizieren, weil er den State-Handler ausführt, bevor er die GET-Anfrage an den echten Service schickt.
139+
- Der Datenbankzustand wird zwischen den Interaktionen automatisch zurückgesetzt, sodass die Tests unabhängig voneinander laufen.
140+

customer-client/test/create-customer.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,38 @@
1414
* limitations under the License.
1515
*/
1616
import { test, expect } from '@playwright/test';
17-
import { createProvider, setupApiProxy } from './pact-proxy';
17+
import { setupApiProxy } from './pact-proxy';
18+
import { PactV4 } from '@pact-foundation/pact';
19+
import path from 'path';
1820

1921
test.describe('Neuer Kunde', () => {
2022
test('zeigt Fehler bei leerem Namen', async ({ page }) => {
2123
// Given
2224
await page.goto('/customers/new');
2325

2426
// When
25-
await page.getByLabel('Name *').fill('Max');
27+
await page.getByLabel('Name *').fill('Sherlock');
2628
await page.getByLabel('Name *').fill('');
2729

2830
// Then
2931
await expect(page.locator('.field-error')).toContainText('Name ist erforderlich');
3032
});
3133

3234
test('erstellt Kunden erfolgreich und navigiert zur Liste', async ({ page }) => {
33-
const provider = createProvider();
35+
const provider = new PactV4({
36+
consumer: 'create-customer',
37+
provider: 'customer-service',
38+
dir: path.resolve(process.cwd(), '../pacts'),
39+
logLevel: 'warn',
40+
});
3441

3542
provider
3643
.addInteraction()
3744
.uponReceiving('a request to create a customer')
3845
.withRequest('POST', '/customers/', (builder) => {
3946
builder
4047
.headers({ 'Content-Type': 'application/json' })
41-
.jsonBody({ name: 'Max Mustermann' });
48+
.jsonBody({ name: 'Sherlock Holmes' });
4249
})
4350
.willRespondWith(201);
4451

@@ -51,17 +58,18 @@ test.describe('Neuer Kunde', () => {
5158
{ number: '007', name: 'James Bond' },
5259
{ number: '0815', name: 'Max Mustermann' },
5360
{ number: '0816', name: 'Erika Mustermann' },
61+
{ number: '1', name: 'Sherlock Holmes' },
5462
]);
5563
})
5664
.executeTest(async (mockServer) => {
5765
await setupApiProxy(page, mockServer.url);
5866
await page.goto('/customers/new');
5967

6068
// When
61-
await page.getByLabel('Name *').fill('Max Mustermann');
69+
await page.getByLabel('Name *').fill('Sherlock Holmes');
6270
await page.getByRole('button', { name: 'Kunde erstellen' }).click();
6371

64-
await expect(page.getByRole('cell', { name: 'Max Mustermann' })).toBeVisible();
72+
await expect(page.getByRole('cell', { name: 'Sherlock Holmes' })).toBeVisible();
6573
});
6674
});
6775
});

0 commit comments

Comments
 (0)