NewIntroducing QODEX QA Services — platform-powered QA for API-driven teams.Learn more →
API Testing7 min read

API-Integrationstesting: Strategien, Tools und Best Practices

S
Shreya Srivastava
Content Team
Updated on: February 2026
API-Integrationstesting: Strategien, Tools und Best Practices

Einleitung

Unit-Tests prüfen einzelne Funktionen. API-Tests prüfen einzelne Endpunkte. Aber keiner sagt Ihnen, ob Ihre Services tatsächlich zusammenarbeiten. Das ist die Aufgabe des Integrationstestings.

API-Integrationstesting validiert, dass mehrere Services, Datenbanken und externe Systeme korrekt über ihre API-Schnittstellen kommunizieren. In einer Microservices-Architektur, in der Dutzende von Services voneinander abhängig sind, ist Integrationstesting das Sicherheitsnetz, das Vertragsabweichungen, Netzwerkfehler und Datenserialisierungsprobleme erkennt.

Dieser Leitfaden behandelt die Strategien, Tools, Code-Beispiele und Best Practices, die Sie benötigen, um zuverlässige API-Integrationstests zu erstellen, von einfachen Zwei-Service-Szenarien bis hin zu komplexen Multi-Service-Workflows.

Was ist API-Integrationstesting?

API-Integrationstesting prüft, ob verbundene Systeme korrekt über ihre API-Schnittstellen zusammenarbeiten. Im Gegensatz zu Unit-Tests (die Code isoliert testen) oder End-to-End-Tests (die das gesamte System über die UI testen) konzentriert sich Integrationstesting auf die Grenzen zwischen Services.

Was Integrationstests validieren

  • Daten fließen korrekt zwischen Services (Service A sendet Daten, die Service B parsen kann)
  • Verträge werden eingehalten (die API gibt die Felder und Typen zurück, die der Consumer erwartet)
  • Fehlerbehandlung funktioniert über Service-Grenzen hinweg (Service A behandelt die Fehler von Service B korrekt)
  • Authentifizierung wird korrekt weitergeleitet durch die Service-Kette
  • Datenbankinteraktionen funktionieren korrekt (Abfragen, Transaktionen, Migrationen)
  • Externe APIs verhalten sich wie erwartet (Zahlungsgateways, E-Mail-Dienste, Drittanbieter-Daten)

Integrationstesting vs. andere Testtypen

TesttypUmfangGeschwindigkeitAbhängigkeitenErkennt
Unit-TestingEinzelne Funktion/KlasseSchnell (ms)GemocktLogikfehler
API-TestingEinzelner EndpunktSchnell (ms-s)Oft gemocktAPI-Vertragsverletzungen
IntegrationstestingMehrere ServicesMittel (s)Real oder ContainerSchnittstellenabweichungen
E2E-TestingVollständiges System + UILangsam (min)Alle realNutzer-Workflow-Fehler

API-Integrationstesting-Strategien

Strategie 1: Big-Bang-Integrationstesting

Alle Services auf einmal verbinden und das vollständige System testen. Einfach zu verstehen, aber schwer zu debuggen, wenn Tests fehlschlagen, da man nicht weiß, welcher Service den Fehler verursacht hat.

Geeignet für: Kleine Systeme mit wenigen Services.

Strategie 2: Inkrementelles Integrationstesting

Services nacheinander hinzufügen und testen. Mit dem Kern-Service beginnen, dann verbundene Services schrittweise hinzufügen.

Top-down: Mit dem API-Gateway beginnen und nachgelagerte Services mocken, dann Mocks nacheinander durch echte Services ersetzen.

Bottom-up: Mit den untersten Services (Datenbank, Cache) beginnen und nach oben aufbauen.

Sandwich: Top-down und Bottom-up kombinieren und in der Mitte zusammentreffen.

Geeignet für: Mittlere bis große Systeme, bei denen Sie Fehler isolieren müssen.

Strategie 3: Contract-Testing

Verträge zwischen Consumer und Provider definieren, dann jede Seite unabhängig verifizieren. Dies ist der skalierbarste Ansatz für Microservices.

Geeignet für: Microservices-Architekturen mit vielen Inter-Service-Abhängigkeiten.

Praktisches API-Integrationstesting mit Code

JavaScript: Service-Integration mit Supertest testen

// tests/integration/orders.test.js
const request = require('supertest');
const app = require('../../src/app');
const db = require('../../src/db');

describe('Orders API Integration', () => { let userId; let productId;

beforeAll(async () => { // Seed database with test data await db.migrate.latest(); const user = await db('users').insert({ name: 'Test User', email: 'test@example.com' }).returning('id'); userId = user[0].id;

const product = await db('products').insert({
  name: 'Widget',
  price: 29.99,
  stock: 100
}).returning('id');
productId = product[0].id;

});

afterAll(async () => { await db('orders').del(); await db('products').del(); await db('users').del(); await db.destroy(); });

test('Creating an order updates product stock', async () => { // Create order via API const orderRes = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 3 }] }) .expect(201);

expect(orderRes.body.total).toBe(89.97); // 29.99 * 3

// Verify stock was decremented
const productRes = await request(app)
  .get(`/api/products/${productId}`)
  .expect(200);

expect(productRes.body.stock).toBe(97); // 100 - 3

});

test('Order fails when insufficient stock', async () => { const res = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 9999 }] }) .expect(400);

expect(res.body.error).toContain('Insufficient stock');

});

test('Order creation sends notification to user service', async () => { const orderRes = await request(app) .post('/api/orders') .send({ userId, items: [{ productId, quantity: 1 }] }) .expect(201);

// Verify notification was created
const notifRes = await request(app)
  .get(`/api/users/${userId}/notifications`)
  .expect(200);

const orderNotif = notifRes.body.find(
  n => n.type === 'order_confirmation'
);
expect(orderNotif).toBeDefined();
expect(orderNotif.orderId).toBe(orderRes.body.id);

}); });

Python: Testen mit pytest und Docker

# tests/integration/test_order_flow.py
import pytest
import requests
import time

API_URL = "http://localhost:3000/api"

@pytest.fixture(scope="module") def test_user(): """Create a test user and return their data.""" response = requests.post(f"{API_URL}/users", json={ "name": "Integration Test User", "email": "integration@test.com" }) assert response.status_code == 201 yield response.json() # Cleanup requests.delete(f"{API_URL}/users/{response.json()['id']}")

@pytest.fixture(scope="module") def test_product(): """Create a test product.""" response = requests.post(f"{API_URL}/products", json={ "name": "Test Widget", "price": 19.99, "stock": 50 }) assert response.status_code == 201 yield response.json() requests.delete(f"{API_URL}/products/{response.json()['id']}")

class TestOrderIntegration: def test_complete_order_flow(self, test_user, test_product): """Test the full order lifecycle across services.""" # Step 1: Create order order_response = requests.post(f"{API_URL}/orders", json={ "userId": test_user["id"], "items": [{"productId": test_product["id"], "quantity": 2}] }) assert order_response.status_code == 201 order = order_response.json() assert order["total"] == 39.98 # 19.99 * 2

    # Step 2: Verify payment was processed
    payment_response = requests.get(
        f"{API_URL}/orders/{order['id']}/payment"
    )
    assert payment_response.status_code == 200
    assert payment_response.json()["status"] == "completed"

    # Step 3: Verify inventory updated
    product_response = requests.get(
        f"{API_URL}/products/{test_product['id']}"
    )
    assert product_response.status_code == 200
    assert product_response.json()["stock"] == 48  # 50 - 2

def test_order_rollback_on_payment_failure(self, test_user, test_product):
    """Verify stock is restored when payment fails."""
    initial_stock = requests.get(
        f"{API_URL}/products/{test_product['id']}"
    ).json()["stock"]

    # Create order with invalid payment method to trigger failure
    order_response = requests.post(f"{API_URL}/orders", json={
        "userId": test_user["id"],
        "items": [{"productId": test_product["id"], "quantity": 1}],
        "paymentMethod": "invalid_card"
    })
    assert order_response.status_code == 400

    # Verify stock was not decremented
    current_stock = requests.get(
        f"{API_URL}/products/{test_product['id']}"
    ).json()["stock"]
    assert current_stock == initial_stock

Contract-Testing mit Pact

Contract-Testing ist der effektivste Ansatz für das Testen von API-Integrationen in Microservices. Der Consumer definiert, was er vom Provider erwartet, und beide Seiten verifizieren unabhängig voneinander.

Consumer-seitiger Test (JavaScript)

// consumer/tests/userServiceClient.pact.test.js
const { PactV3 } = require('@pact-foundation/pact');
const { UserServiceClient } = require('../src/userServiceClient');

const provider = new PactV3({ consumer: 'OrderService', provider: 'UserService', });

describe('UserService Client', () => { test('fetches user by ID', async () => { provider .given('a 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', }, });

await provider.executeTest(async (mockServer) => {
  const client = new UserServiceClient(mockServer.url);
  const user = await client.getUser(1);

  expect(user.id).toBe(1);
  expect(user.name).toBe('John Doe');
});

}); });

Provider-seitige Verifizierung

// provider/tests/pactVerification.test.js
const { Verifier } = require('@pact-foundation/pact');

describe('UserService Provider Verification', () => { test('validates contract with OrderService', async () => { const verifier = new Verifier({ providerBaseUrl: 'http://localhost:3001', pactUrls: ['./pacts/OrderService-UserService.json'], stateHandlers: { 'a user with ID 1 exists': async () => { // Set up the required state in the provider await db('users').insert({ id: 1, name: 'John Doe', email: 'john@example.com', }); }, }, });

await verifier.verifyProvider();

}); });

Docker für Integrationstesting verwenden

Integrationstests benötigen echte Abhängigkeiten (Datenbanken, Caches, Message-Queues). Docker Compose macht dies handhabbar:

# docker-compose.test.yml
version: '3.8'
services:
  api:
    build: .
    environment:
      DATABASE_URL: postgres://test:test@db:5432/testdb
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

db: image: postgres:16 environment: POSTGRES_DB: testdb POSTGRES_USER: test POSTGRES_PASSWORD: test healthcheck: test: pg_isready -U test interval: 5s retries: 5

cache: image: redis:7-alpine

test-runner: build: context: . dockerfile: Dockerfile.test environment: API_URL: http://api:3000 depends_on: - api command: npm run test:integration

# Run integration tests
docker-compose -f docker-compose.test.yml up --build --abort-on-container-exit

Integrationstesting in CI/CD

# GitHub Actions integration tests
name: Integration Tests
on:
  push:
    branches: [main, develop]

jobs:
  integration:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run migrate
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/testdb
      - run: npm run test:integration
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

Best Practices für API-Integrationstesting

1. Echte Abhängigkeiten wo möglich verwenden

Integrationstests mit gemockten Abhängigkeiten sind keine Integrationstests, sondern Unit-Tests in Verkleidung. Verwenden Sie echte Datenbanken, echte Caches und echte Message-Queues über Docker-Container.

2. Testdaten isolieren

Jeder Test sollte seine eigenen Daten erstellen und danach bereinigen. Verwenden Sie Datenbanktransaktionen, die nach jedem Test zurückgerollt werden, oder leeren Sie Tabellen zwischen Test-Runs.

3. Fehlerszenarien testen

Testen Sie nicht nur den Happy Path. Testen Sie, was passiert, wenn Abhängigkeiten nicht verfügbar sind, Fehler zurückgeben oder unerwartete Daten liefern.

4. Tests schnell halten

Integrationstests sind langsamer als Unit-Tests, aber sie sollten nicht zu langsam sein. Ziel ist unter 30 Sekunden für Ihre vollständige Integrationssuite. Verwenden Sie parallele Ausführung und geteiltes Setup wo sinnvoll.

5. Contract-Testing für teamübergreifende APIs verwenden

Wenn verschiedene Teams verschiedene Services besitzen, ist Contract-Testing (mit Pact oder ähnlichen Tools) praktikabler als das gleichzeitige Ausführen aller Services.

6. Mit anderen Testtypen kombinieren

Integrationstests ergänzen REST-API-Tests, Load-Tests und Security-Tests. Verwenden Sie Qodex.ai, um funktionale und Security-Tests automatisch zu generieren, und fügen Sie dann Integrationstests für Cross-Service-Workflows hinzu.

Einen vollständigen Überblick über Testing-Tools finden Sie in unserem API-Testing-Tools-Vergleich.


Häufig gestellte Fragen

Was ist der Unterschied zwischen API-Testing und API-Integrationstesting?

API-Testing validiert einen einzelnen API-Endpunkt isoliert: korrekte Statuscodes, Response-Bodies und Fehlerbehandlung. API-Integrationstesting validiert, dass mehrere Services über ihre APIs zusammenarbeiten, Daten korrekt fließen, Verträge eingehalten werden und Fehler korrekt über Service-Grenzen hinweg weitergegeben werden.

Ist API-Testing dasselbe wie Integrationstesting?

Nicht genau. API-Testing kann isoliert durchgeführt werden (mit gemockten Abhängigkeiten), was es eher dem Unit-Testing ähnelt. Integrationstesting prüft speziell die Interaktionen zwischen echten Services, Datenbanken und externen Systemen. Es gibt jedoch erhebliche Überschneidungen, und viele Teams verwenden API-Tests als Integrationstests, wenn sie gegen echte Abhängigkeiten testen.

Wie teste ich API-Integration ohne Zugang zum echten Service?

Verwenden Sie Contract-Testing mit Tools wie Pact. Der Consumer definiert erwartete Interaktionen, und beide Seiten verifizieren unabhängig voneinander. Sie können auch Mock-Server, WireMock oder Service-Virtualisierungs-Tools verwenden, um den externen Service zu simulieren.

Welche Tools eignen sich am besten für API-Integrationstesting?

Für JavaScript: Supertest + Jest mit Docker. Für Python: pytest + requests mit Docker. Für Contract-Testing: Pact. Für automatisierte Testgenerierung: Qodex.ai. Für Cross-Service-Testing: Docker Compose zur Orchestrierung aller Services.

Wie gehe ich mit Testdaten in Integrationstests um?

Verwenden Sie Datenbanktransaktionen, die nach jedem Test zurückgerollt werden, oder leeren Sie Tabellen zwischen Test-Runs. Erstellen Sie Testdaten in setUp/beforeEach-Hooks und bereinigen Sie in tearDown/afterEach. Teilen Sie niemals Testdaten zwischen Tests, jeder Test sollte unabhängig sein.

Sollten Integrationstests in CI/CD laufen?

Ja. Integrationstests sollten bei jedem Push und Pull Request laufen. Verwenden Sie Docker Compose oder CI/CD-Service-Container (wie GitHub Actions Services), um echte Abhängigkeiten hochzufahren. Halten Sie die Suite schnell, indem Sie sich auf kritische Integrationspfade konzentrieren und Tests parallel ausführen.