Contract Testing für APIs | Enjyn Gruppe
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

235 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

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
120