RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Capstone: Full-Stack AI App
  6. /Ch. 7
Capstone: Full-Stack AI App

07. Integration Testing

Chapter 7 of 18 · 20 min
KEY INSIGHT

Integration tests catch the bugs that unit tests miss—specifically, the interaction failures between services and the async timing issues in streaming code.

Integration tests verify that components work together correctly. For an AI application, this means testing the full request path from API gateway through backend to model serving. Tests must handle async operations and streaming responses.

The test setup uses pytest with pytest-asyncio for async support. A Docker Compose test fixture spins up all services with test data:

# tests/conftest.py
import pytest
import docker
from docker.types import ServicePort, EndpointSpec

@pytest.fixture(scope="session")
def docker_services(docker_api_client):
    client = docker.from_env()
    
    # Start postgres for test data
    postgres = client.containers.run(
        "postgres:15",
        name="test-postgres",
        environment={"POSTGRES_DB": "test_db", "POSTGRES_PASSWORD": "test"},
        ports={"5432/tcp": None},
        remove=True,
        detach=True
    )
    
    # Start mock model server for predictable responses
    mock_model = client.containers.run(
        "nginx:alpine",
        name="test-model-server",
        ports={"8080/tcp": None},
        remove=True,
        detach=True
    )
    
    yield {
        "postgres": postgres,
        "model_server": mock_model
    }
    
    docker_services.cleanup()

Integration tests for the upload flow verify file handling, progress reporting, and database persistence:

# tests/test_upload.py
import pytest
from httpx import AsyncClient

@pytest.mark.asyncio
async def test_upload_pdf_success(docker_services):
    async with AsyncClient(base_url="http://localhost:8000") as client:
        with open("tests/fixtures/sample.pdf", "rb") as f:
            files = {"file": ("sample.pdf", f, "application/pdf")}
            async with client.stream("POST", "/api/v1/upload", files=files) as response:
                assert response.status_code == 200
                
                data = await response.json()
                assert "document_id" in data
                assert response.headers["content-type"] == "application/json"

@pytest.mark.asyncio
async def test_upload_size_limit(docker_services):
    async with AsyncClient(base_url="http://localhost:8000") as client:
        # Generate 60MB file (over 50MB limit)
        large_content = b"x" * (60 * 1024 * 1024)
        
        files = {"file": ("large.pdf", large_content, "application/pdf")}
        response = await client.post("/api/v1/upload", files=files)
        
        assert response.status_code == 413
        assert "File too large" in response.text

The streaming response test requires careful handling of the SSE format:

# tests/test_chat.py
@pytest.mark.asyncio
async def test_ask_streaming_response(docker_services):
    async with AsyncClient(base_url="http://localhost:8000") as client:
        payload = {
            "question": "What is this document about?",
            "document_id": "test-doc-123"
        }
        
        chunks = []
        async with client.stream("POST", "/api/v1/ask", json=payload) as response:
            assert response.status_code == 200
            
            async for line in response.aiter_lines():
                if line.startswith("data: "):
                    chunks.append(line[6:])  # Strip "data: " prefix
        
        full_response = "".join(chunks)
        assert len(full_response) > 0
        assert "error" not in full_response.lower()

Common integration test failures include timing issues with async services, resource contention between tests, and test data pollution. Use unique identifiers for test documents to avoid cross-test interference.

EXERCISE

Write integration tests covering the complete flow: upload document, wait for processing, ask question, receive streaming response.

← Chapter 6
UI/UX Design
Chapter 8 →
Performance Testing