running-integration-tests
SKILL.md
Integration Test Runner
Overview
Execute integration tests that validate interactions between multiple components, services, and external systems. Tests real database queries, API calls between services, message queue publishing/consuming, and file system operations without mocking the integration boundary.
Prerequisites
- Integration test framework installed (Jest + Supertest, pytest, JUnit 5, or Go testing)
- External services running (database, cache, message queue) via Docker Compose or Testcontainers
- Database migrations applied and seed data loaded
- Test configuration with connection strings pointing to test instances (not production)
- Sufficient timeout settings (integration tests are slower than unit tests)
Instructions
- Identify integration boundaries to test:
- API routes with database queries (controller-to-repository flow).
- Service-to-service HTTP communication.
- Message queue producers and consumers.
- File upload/download with storage services.
- Cache read/write operations (Redis, Memcached).
- Set up test infrastructure:
- Start required services using
docker-compose -f docker-compose.test.yml up -d. - Or use Testcontainers to programmatically start/stop containers per test suite.
- Run database migrations against the test database.
- Seed baseline data required by the test suite.
- Start required services using
- Write integration tests following these patterns:
- API integration: Send HTTP requests via Supertest and assert responses including headers, status, and body.
- Database integration: Execute the service method and verify database state with direct queries.
- Event integration: Publish a message and verify the consumer processes it correctly.
- Use real implementations, not mocks, at the integration boundary.
- Manage test data isolation:
- Wrap each test in a database transaction and roll back after assertion.
- Or truncate tables in
beforeEachand re-seed minimum required data. - Use unique identifiers per test to avoid collisions in shared databases.
- Handle asynchronous operations:
- Poll for expected state changes with timeout (e.g., wait for queue consumer to process).
- Use event listeners or callbacks to signal completion.
- Set generous timeouts (10-30 seconds) for external service interactions.
- Run integration tests separately from unit tests:
- Tag with
@integrationor place in a separate directory (tests/integration/). - Configure CI to run integration tests in a dedicated job with service containers.
- Tag with
- Generate test results in JUnit XML format for CI reporting.
Output
- Integration test files in
tests/integration/organized by feature - Docker Compose test configuration for service dependencies
- Database seed and teardown scripts
- JUnit XML test results for CI consumption
- Integration test coverage report showing tested integration points
Error Handling
| Error | Cause | Solution |
|---|---|---|
| Connection refused to database | Database container not yet ready | Add wait-for-it.sh or health check polling before running tests; increase startup timeout |
| Foreign key constraint violation | Test data inserted in wrong order or cleanup incomplete | Seed data in dependency order; use cascading deletes in teardown; wrap in transactions |
| Flaky test due to race condition | Async consumer has not processed the message yet | Use polling with timeout instead of fixed sleep; add event completion callbacks |
| Test passes locally, fails in CI | CI uses different service versions or network config | Pin Docker image versions; verify environment variables match; check CI service container logs |
| Slow test suite (>5 minutes) | Too many integration tests or insufficient parallelization | Run independent test suites in parallel CI jobs; use Testcontainers reuse mode; limit seed data |
Examples
Supertest API integration test:
import request from 'supertest';
import { app } from '../src/app';
import { db } from '../src/database';
describe('POST /api/users', () => {
beforeEach(async () => { await db.query('DELETE FROM users'); });
afterAll(async () => { await db.end(); });
it('creates a user and persists to database', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@example.com' })
.expect(201); # HTTP 201 Created
expect(response.body).toMatchObject({ name: 'Alice' });
const row = await db.query('SELECT * FROM users WHERE email = $1', ['alice@example.com']);
expect(row.rows).toHaveLength(1);
});
});
pytest with database transaction rollback:
import pytest
from myapp.services import UserService
@pytest.fixture
def db_session(test_database):
session = test_database.begin_nested()
yield session
session.rollback()
def test_create_user_persists_to_db(db_session):
service = UserService(db_session)
user = service.create(name="Alice", email="alice@test.com")
assert user.id is not None
found = db_session.query(User).filter_by(email="alice@test.com").one()
assert found.name == "Alice"
Resources
- Supertest: https://github.com/ladjs/supertest
- Testcontainers: https://testcontainers.com/
- pytest database fixtures: https://docs.pytest.org/en/stable/how-to/fixtures.html
- Docker Compose for testing: https://docs.docker.com/compose/use-cases/#testing
- Integration testing strategies: https://martinfowler.com/bliki/IntegrationTest.html
Weekly Installs
22
Repository
jeremylongshore…s-skillsGitHub Stars
1.6K
First Seen
Feb 1, 2026
Security Audits
Installed on
opencode20
codex20
github-copilot19
kimi-cli19
amp19
cline19