Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

234 Dokumentationen verfügbar

Wissensdatenbank

Contract Testing APIs

Zuletzt aktualisiert: 20.01.2026 um 11:24 Uhr

Contract Testing für APIs

Contract Testing stellt sicher, dass API-Provider und -Consumer kompatibel bleiben. Lernen Sie Consumer-Driven Contracts mit Pact zu implementieren.

Das Problem

┌─────────────────────────────────────────────────────────────┐
│                 INTEGRATION TESTING PROBLEM                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Consumer (Frontend)         Provider (API)                │
│   ┌─────────────┐            ┌─────────────┐               │
│   │   Tests ✓   │            │   Tests ✓   │               │
│   │   Build ✓   │            │   Build ✓   │               │
│   │   Deploy ✓  │            │   Deploy ✓  │               │
│   └──────┬──────┘            └──────┬──────┘               │
│          │                          │                       │
│          │     Production           │                       │
│          │         💥              │                       │
│          └──────────┴───────────────┘                       │
│                                                             │
│   PROBLEM: Beide Tests sind grün, aber zusammen             │
│            funktioniert es nicht!                           │
│                                                             │
│   URSACHE:                                                  │
│   • Consumer erwartet: { "user_name": "John" }             │
│   • Provider liefert:  { "userName": "John" }              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Contract Testing Lösung

┌─────────────────────────────────────────────────────────────┐
│                   CONTRACT TESTING                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   1. CONSUMER generiert Contract                            │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ Consumer Test:                                       │  │
│   │ "Wenn ich GET /users/1 aufrufe,                     │  │
│   │  erwarte ich { user_name: string, email: string }"  │  │
│   │                                                      │  │
│   │ → Generiert: contract.json (Pact File)              │  │
│   └─────────────────────────────────────────────────────┘  │
│                         │                                   │
│                         ▼                                   │
│   2. CONTRACT wird geteilt (Pact Broker)                   │
│   ┌─────────────────────────────────────────────────────┐  │
│   │              ┌────────────────┐                      │  │
│   │              │  Pact Broker   │                      │  │
│   │              │  (Contract DB) │                      │  │
│   │              └────────────────┘                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                         │                                   │
│                         ▼                                   │
│   3. PROVIDER verifiziert Contract                         │
│   ┌─────────────────────────────────────────────────────┐  │
│   │ Provider Test:                                       │  │
│   │ "Erfülle ich alle Contracts meiner Consumer?"       │  │
│   │                                                      │  │
│   │ → Testet echte API gegen Contract-Erwartungen       │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Consumer-Driven Contracts mit Pact (JavaScript)

// CONSUMER SEITE

// userService.test.js - Consumer Test
const { Pact } = require('@pact-foundation/pact');
const { fetchUser } = require('./userService');

describe('User Service', () => {
    const provider = new Pact({
        consumer: 'Frontend',
        provider: 'UserAPI',
        port: 1234,
        log: './logs/pact.log',
        dir: './pacts',
    });

    beforeAll(() => provider.setup());
    afterAll(() => provider.finalize());
    afterEach(() => provider.verify());

    describe('get user by id', () => {
        it('returns user when user exists', async () => {
            // ARRANGE: Erwartung definieren
            await provider.addInteraction({
                state: 'user with id 1 exists',
                uponReceiving: 'a request for user 1',
                withRequest: {
                    method: 'GET',
                    path: '/api/users/1',
                    headers: {
                        Accept: 'application/json',
                    },
                },
                willRespondWith: {
                    status: 200,
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: {
                        id: 1,
                        name: 'John Doe',
                        email: 'john@example.com',
                    },
                },
            });

            // ACT: Consumer Code ausführen
            const user = await fetchUser(1);

            // ASSERT
            expect(user).toEqual({
                id: 1,
                name: 'John Doe',
                email: 'john@example.com',
            });
        });

        it('returns 404 when user not found', async () => {
            await provider.addInteraction({
                state: 'user with id 999 does not exist',
                uponReceiving: 'a request for non-existent user',
                withRequest: {
                    method: 'GET',
                    path: '/api/users/999',
                    headers: {
                        Accept: 'application/json',
                    },
                },
                willRespondWith: {
                    status: 404,
                    body: {
                        error: 'User not found',
                    },
                },
            });

            await expect(fetchUser(999)).rejects.toThrow('User not found');
        });
    });
});
// userService.js - Der Consumer Code

const API_BASE = process.env.API_URL || 'http://localhost:1234';

async function fetchUser(id) {
    const response = await fetch(`${API_BASE}/api/users/${id}`, {
        headers: { Accept: 'application/json' },
    });

    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error);
    }

    return response.json();
}

module.exports = { fetchUser };

Provider Verification

// PROVIDER SEITE

// providerVerification.test.js
const { Verifier } = require('@pact-foundation/pact');
const app = require('./app');  // Express App

describe('Pact Verification', () => {
    let server;

    beforeAll((done) => {
        server = app.listen(3000, done);
    });

    afterAll((done) => {
        server.close(done);
    });

    it('validates the expectations of the consumer', async () => {
        const verifier = new Verifier({
            providerBaseUrl: 'http://localhost:3000',
            provider: 'UserAPI',

            // Pact Files laden
            pactUrls: ['./pacts/frontend-userapi.json'],

            // Oder von Pact Broker
            // pactBrokerUrl: 'https://your-broker.pactflow.io',
            // pactBrokerToken: process.env.PACT_BROKER_TOKEN,

            // Provider States Handler
            stateHandlers: {
                'user with id 1 exists': async () => {
                    // Test-Daten in DB einfügen
                    await db.users.create({
                        id: 1,
                        name: 'John Doe',
                        email: 'john@example.com',
                    });
                },
                'user with id 999 does not exist': async () => {
                    // Sicherstellen dass User nicht existiert
                    await db.users.deleteWhere({ id: 999 });
                },
            },

            // Provider States aufräumen
            beforeEach: async () => {
                await db.users.truncate();
            },
        });

        await verifier.verifyProvider();
    });
});

Pact mit Matching

const { Matchers } = require('@pact-foundation/pact');
const { like, eachLike, term, integer, uuid } = Matchers;

await provider.addInteraction({
    state: 'users exist',
    uponReceiving: 'a request for all users',
    withRequest: {
        method: 'GET',
        path: '/api/users',
    },
    willRespondWith: {
        status: 200,
        body: {
            // Array mit mindestens einem Element
            users: eachLike({
                // Typ-Matching statt exakter Werte
                id: integer(1),
                name: like('John Doe'),
                email: term({
                    matcher: '.*@.*\\..*',
                    generate: 'john@example.com',
                }),
                uuid: uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a'),
                createdAt: like('2024-01-15T10:30:00Z'),
            }),
            pagination: {
                page: integer(1),
                totalPages: integer(5),
                totalItems: integer(100),
            },
        },
    },
});

// Matchers:
// like()      - Typ muss übereinstimmen, nicht Wert
// eachLike()  - Array mit Elementen dieses Typs
// term()      - Regex-Matching
// integer()   - Integer-Typ
// uuid()      - UUID-Format
// boolean()   - Boolean-Typ

Pact Broker

# Docker Compose für Pact Broker
version: '3'
services:
  pact-broker:
    image: pactfoundation/pact-broker
    ports:
      - "9292:9292"
    environment:
      PACT_BROKER_DATABASE_URL: postgres://postgres:password@postgres/pact_broker
      PACT_BROKER_BASIC_AUTH_USERNAME: admin
      PACT_BROKER_BASIC_AUTH_PASSWORD: admin
    depends_on:
      - postgres

  postgres:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: pact_broker
// Contracts zum Broker publizieren
// package.json scripts
{
  "scripts": {
    "pact:publish": "pact-broker publish ./pacts --consumer-app-version=$npm_package_version --broker-base-url=$PACT_BROKER_URL --broker-token=$PACT_BROKER_TOKEN"
  }
}

// In CI/CD
npm run pact:publish

// Provider Verification vom Broker
const verifier = new Verifier({
    providerBaseUrl: 'http://localhost:3000',
    provider: 'UserAPI',
    pactBrokerUrl: process.env.PACT_BROKER_URL,
    pactBrokerToken: process.env.PACT_BROKER_TOKEN,
    publishVerificationResult: true,
    providerVersion: process.env.GIT_SHA,
});

Can-I-Deploy

# Vor Deployment prüfen: Sind alle Contracts erfüllt?

# Consumer fragen: Kann ich deployen?
pact-broker can-i-deploy \
  --pacticipant Frontend \
  --version 1.2.3 \
  --to production \
  --broker-base-url $PACT_BROKER_URL

# Provider fragen: Kann ich deployen?
pact-broker can-i-deploy \
  --pacticipant UserAPI \
  --version 2.0.0 \
  --to production \
  --broker-base-url $PACT_BROKER_URL

# Exit Code 0 = Ja, alle Contracts verifiziert
# Exit Code 1 = Nein, Breaking Changes

# In CI/CD Pipeline
- name: Can I Deploy?
  run: |
    pact-broker can-i-deploy \
      --pacticipant $SERVICE_NAME \
      --version ${{ github.sha }} \
      --to production

CI/CD Integration

# .github/workflows/consumer.yml

name: Consumer CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Run Pact tests
        run: npm test

      - name: Publish Pacts
        run: |
          npm run pact:publish
        env:
          PACT_BROKER_URL: ${{ secrets.PACT_BROKER_URL }}
          PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}

      - name: Can I Deploy?
        run: |
          pact-broker can-i-deploy \
            --pacticipant Frontend \
            --version ${{ github.sha }} \
            --to production
# .github/workflows/provider.yml

name: Provider CI

on:
  push:
  workflow_dispatch:
    inputs:
      pact_url:
        description: 'Pact URL to verify (webhook trigger)'

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: npm ci

      - name: Start Provider
        run: npm start &

      - name: Verify Pacts
        run: npm run pact:verify
        env:
          PACT_BROKER_URL: ${{ secrets.PACT_BROKER_URL }}
          PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
          PACT_URL: ${{ github.event.inputs.pact_url }}

      - name: Can I Deploy?
        run: |
          pact-broker can-i-deploy \
            --pacticipant UserAPI \
            --version ${{ github.sha }} \
            --to production

Contract Testing vs Integration Testing

Aspekt Contract Testing Integration Testing
Geschwindigkeit Schnell (isoliert) Langsam (echte Services)
Unabhängigkeit Teams können unabhängig testen Alle Services müssen laufen
Feedback Sofort bei Contract-Bruch Erst bei Integration
Scope API-Schnittstelle End-to-End Verhalten
Ersetzt Nein, ergänzt -
💡 Best Practices: 1. Consumer-First: Consumer definiert was er braucht
2. Matchers statt exakte Werte für Flexibilität
3. Pact Broker für zentrales Contract-Management
4. can-i-deploy vor jedem Deployment
5. Provider States für testbare Szenarien

Weitere Informationen