Claude Fable 5.1 & GPT-6 Astra packages are live

Integration

Free

Testing across real boundaries — real databases in containers, isolation between tests, and faking third parties without faking their behaviour.

206 lines8.3 KB Sarvam Ai Testing
targetModels
Sarvam-105BSarvam-30BSarvam FamilyFuture Sarvam Models
name
integration
category
Testing
description
Testing across real boundaries — real databases in containers, isolation between tests, and faking third parties without faking their behaviour.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Sarvam: scripts/model-profiles.json -->

#Locale

Examples use Indian conventions: ₹ amounts, IST, dd/mm/yyyy, Aadhaar and DPDP Act where a standard mentions identity or privacy law. Keep them when you copy an example.


#Purpose

Rules for tests that exercise more than one component together — your code plus a real database, a real HTTP layer, a real queue.

Integration tests exist to catch what unit tests structurally cannot: wrong SQL, a missing migration, a serialisation mismatch, a transaction that does not roll back, middleware ordering. Every one of those bugs passes a suite of green unit tests, because the mock agreed with the misunderstanding.


#Use the real dependency

Run the actual database, not an in-memory substitute.

js
// SQLite standing in for Postgres will not catch: JSONB operators, array
// columns, `ON CONFLICT` semantics, partial indexes, or citext behaviour.
const container = await new PostgreSqlContainer("postgres:16").start();
process.env.DATABASE_URL = container.getConnectionUri();
await migrate();                       // run real migrations, not a schema dump
  • Use Testcontainers, Docker Compose, or a dedicated test instance. Pin the same major version as production.
  • Run the real migrations in the test setup. This is how you learn that migration 47 fails on a table with data — a class of failure no other test catches.
  • Never substitute SQLite for Postgres or MySQL. The dialects differ where bugs live.

#Isolation

Tests must not see each other's data, and must be safe to run in parallel.

StrategySpeedIsolationNotes
Transaction rollbackFastestStrongCannot test code that commits or uses its own transactions
Truncate between testsFastStrongSimple and predictable; the sane default
Schema per workerFastStrongBest for parallel runs
Database per workerSlowerStrongestHeaviest, most faithful
Shared database, no cleanupNoneOrder-dependent; never do this
js
afterEach(async () => {
  // Truncate every table in one statement; RESTART IDENTITY keeps ids stable
  await db.$executeRawUnsafe(`
    TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY CASCADE
  `);
});

Never rely on tests running in a particular order, and never let one test depend on data another created. Each test builds what it needs.

Prefer factories over shared fixtures — a function that creates a valid record with sensible defaults and accepts overrides. A fixture file that every test reads becomes a coupling point nobody dares change.


#Third parties

Do not call real external APIs from tests. They are slow, rate-limited, and turn an unrelated outage into a red build.

ApproachWhen
HTTP-level fake (msw, nock, WireMock)Default — intercepts at the boundary, exercises your real client code
Provider sandbox (Stripe test mode)Where behaviour is complex and the sandbox is faithful
Contract tests (Pact)Where both sides are yours and can be verified
Mocking the SDK objectAvoid — tests your assumption about the SDK

Record real responses once and replay them, so the fake reflects what the provider actually returns rather than what the documentation claims:

js
server.use(
  http.post("https://api.stripe.com/v1/charges", () =>
    HttpResponse.json(recordedChargeResponse, { status: 200 })
  )
);

Always fake the failure modes too — timeout, 429, 500, malformed body, partial response. Untested error paths are where integration bugs hide, and the happy path is the one part unit tests already covered.


#Tooling

NeedOptions
Ephemeral dependenciestestcontainers, docker-compose, dockertest
HTTP-level fakingmsw, nock, WireMock, responses
Driving the APIsupertest, hurl, httpx, RestAssured
Contract verificationpact, spring-cloud-contract
Fixturesfishery, factory_bot, factory_boy

Transaction-rollback isolation in practice: open a transaction in beforeEach, hand the transaction-scoped client to the code under test, and ROLLBACK in afterEach. It is the fastest strategy, but it breaks the moment the code under test issues its own BEGIN/COMMIT, or relies on pg_notify, advisory locks, or a connection pool that hands out a second connection. When that happens, fall back to TRUNCATE rather than fighting it.

#What to test at this level

Test hereDo not test here
Query correctness against a real schemaBusiness rules → Testing/unit
Migrations applying cleanly to seeded dataPure calculations → Testing/unit
Transaction and rollback behaviourFull user journeys → Testing/e2e
Authorisation scoping across tenantsCSS and layout → Testing/visual
HTTP contract: status, shape, headersThird-party internals
Serialisation round-trips

Test the API through its real HTTP surface, not by calling handlers directly:

js
const res = await request(app)
  .get("/api/invoices/inv_44c")
  .set("Authorization", `Bearer ${tokenForOtherTenant}`);

expect(res.status).toBe(404);          // not 403 — see Security/authorization

That test catches middleware ordering, serialisation and authorisation together — none of which a direct handler call exercises.


#Keeping them fast

Integration tests are slower by nature; keep them from becoming the reason nobody runs the suite.

  • Start containers once per run, not per test file. Reuse across the suite.
  • Parallelise by worker with a schema or database each.
  • Seed the minimum needed. A 500-row fixture where 2 rows suffice costs on every test.
  • Keep the ratio sane: many unit tests, a meaningful layer of integration tests, a handful of end-to-end tests. Inverting that produces a suite that takes 40 minutes and gets skipped.
  • Run them on every pull request, not nightly. A failure found a day later has already been built on.

#Anti-patterns

Anti-patternWhy it failsFix
SQLite standing in for PostgresDialect differences hide real bugsSame engine and major version
Applying a schema dump, not migrationsMigration bugs never surfaceRun real migrations
Shared database with no cleanupOrder-dependent and flakyTruncate or transaction per test
Tests depending on seed dataBreaks when the seed changesFactories per test
Calling real third-party APIsSlow, rate-limited, externally flakyHTTP-level fake
Mocking the SDK objectTests your assumption of the SDKIntercept at HTTP
Only faking success responsesError paths untestedFake 429, 500, timeout
Calling handlers directlySkips middleware and serialisationDrive the real HTTP surface
Container per test fileSuite takes minutes to startOne per run, reused
Integration tests nightly onlyFailures found a day lateRun on every pull request

#Checklist

  • Verify: Tests run against the same database engine and major version as production
  • Verify: Real migrations are applied in setup, not a schema dump
  • Verify: Each test is isolated by transaction, truncation, or per-worker schema
  • Verify: No test depends on another's data or on execution order
  • Verify: Test data comes from factories with overrides, not shared fixtures
  • Verify: No real external API is called; fakes intercept at the HTTP boundary
  • Verify: Failure modes are faked as well as success
  • Verify: The API is exercised through its real HTTP surface
  • Verify: Cross-tenant authorisation is covered with two tenants
  • Verify: Containers start once per run and tests parallelise by worker
  • Verify: The suite runs on every pull request