{"post":{"slug":"how-to-write-integration-tests-2026","title":"How to write integration tests in 2026","excerpt":"Integration tests catch the bugs unit tests miss by exercising real component boundaries. Here is a practical 2026 guide with patterns that actually ship.","category":"code","date":"March 2, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">I</span>ntegration tests exist in the awkward middle ground between unit tests that run instantly and end-to-end tests that take forever — and most codebases either skip them entirely or write them so poorly that the suite becomes a maintenance burden. In 2026, the tooling has matured enough that there is no excuse for either extreme. You can have fast, reliable integration tests that developers actually run.\n\n## What changed in 2026\n\n- **Testcontainers is now the default.** The JVM library became cross-language; Go, Python, Node, and Rust all have stable Testcontainers clients. Spinning up a real Postgres for a test suite takes ~5 seconds on modern CI hardware.\n- **Contract testing grew up.** Pact and its ecosystem matured. Consumer-driven contracts are now a standard pattern for microservice boundaries, not just a niche practice.\n- **Vitest and pytest-anyio handle async properly.** Testing async database calls and HTTP handlers without workarounds is now straightforward.\n- **Shared container lifecycles.** Testing frameworks now support module-scoped or session-scoped containers, so you pay the startup cost once per suite, not once per test.\n\n## What an integration test is\n\nAn integration test exercises the interaction between two or more real components — your code + a real database, your HTTP handler + a real message queue, your service + a real cache. It does not mock the integration boundary.\n\n| Test type | What it exercises | Typical runtime |\n| --- | --- | --- |\n| Unit test | One function/class, all deps mocked | < 1 ms |\n| Integration test | 2–3 real components together | 20–500 ms |\n| Contract test | API shape between two services | 50–200 ms |\n| E2E test | Full stack, real browser or client | 1–30 s |\n\nThe boundary is judgment: when you mock the database, it becomes a unit test. When you spin up a real database, it becomes an integration test.\n\n## Setting up Testcontainers\n\n```python\n# Python + pytest example\nimport pytest\nfrom testcontainers.postgres import PostgresContainer\nfrom sqlalchemy import create_engine, text\n\n@pytest.fixture(scope=\"session\")\ndef pg_engine():\n    with PostgresContainer(\"postgres:16\") as pg:\n        engine = create_engine(pg.get_connection_url())\n        # run migrations once\n        run_migrations(engine)\n        yield engine\n\ndef test_user_created(pg_engine):\n    with pg_engine.connect() as conn:\n        conn.execute(text(\"INSERT INTO users(name) VALUES ('alice')\"))\n        conn.commit()\n        row = conn.execute(text(\"SELECT name FROM users WHERE name='alice'\")).fetchone()\n    assert row[0] == \"alice\"\n```\n\nThe `scope=\"session\"` key is critical: Postgres starts once for the whole test session, not once per test. This cuts a 100-test suite from 8 minutes to under 30 seconds.\n\n## What changed in 2026\n\nTestcontainers 2.x introduced **reusable containers**: add `reuse=True` and the container survives between test runs in local development, making iterative test runs nearly instant. In CI you still get a fresh container per run for isolation.\n\n## Testing an HTTP handler end-to-end (within a service)\n\n```typescript\n// TypeScript + Vitest + Fastify\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest'\nimport { createApp } from '../src/app'\nimport { GenericContainer } from 'testcontainers'\n\nlet app: any\nlet pg: any\n\nbeforeAll(async () => {\n  pg = await new GenericContainer('postgres:16')\n    .withEnvironment({ POSTGRES_PASSWORD: 'test' })\n    .start()\n  app = await createApp({ dbUrl: pg.getConnectionUri() })\n  await app.listen({ port: 0 })\n})\n\nafterAll(async () => {\n  await app.close()\n  await pg.stop()\n})\n\ndescribe('POST /users', () => {\n  it('creates a user and returns 201', async () => {\n    const res = await app.inject({\n      method: 'POST',\n      url: '/users',\n      payload: { name: 'bob' },\n    })\n    expect(res.statusCode).toBe(201)\n    expect(res.json()).toMatchObject({ name: 'bob' })\n  })\n})\n```\n\n## How to structure your suite\n\nSeparate integration tests from unit tests at the file level and in your CI pipeline:\n\n```\ntests/\n  unit/          # fast, no I/O, run on every save\n  integration/   # testcontainers, run on push\n  contract/      # pact or schemathesis, run on push\n  e2e/           # playwright or k6, run on merge to main\n```\n\nTag integration tests so you can run them selectively:\n\n```bash\n# pytest\npytest tests/integration -m integration --timeout=60\n\n# vitest\nvitest run --project integration\n```\n\n## How to pick what to integration-test\n\n1. **Database access layer** — every query that writes or reads critical state.\n2. **HTTP handlers** — each endpoint's happy path and the important error paths (404, 409, 422).\n3. **Message queue consumers** — publish a message to the real broker, assert the side effects.\n4. **Cache invalidation logic** — the bugs almost always live at the cache + DB boundary.\n5. **Auth middleware** — test with a real token verification, not a mocked one.\n\nSkip testing framework code, generated ORM queries you did not write, and things covered thoroughly by the framework's own tests.\n\n## Common mistakes\n\n**One container per test.** Startup cost dominates. Always scope containers to the session or at minimum the module.\n\n**Flaky async teardown.** Tests fail in CI because previous test data leaks into the next test. Wrap each test in a transaction and roll it back:\n\n```python\n@pytest.fixture(autouse=True)\ndef rollback(pg_engine):\n    with pg_engine.begin() as conn:\n        yield conn\n        conn.rollback()\n```\n\n**Testing too much in one test.** If a single test exercises three services, a message queue, and a cache — split it. When it fails you will not know which component broke.\n\n**Ignoring test data isolation.** Use unique IDs (UUIDs) or per-test schemas. Shared row IDs across concurrent tests cause mysterious race condition failures.\n\n## What to skip\n\n- **Mocking your ORM** inside an integration test — that defeats the point.\n- **Testing Postgres itself** — you trust the database; test your queries.\n- **Slow seed scripts** that rebuild 50 MB of data for each test file — seed once at session scope, clean up at test scope.\n\n## FAQ\n\n**How long should an integration test suite take?**\nUnder 2 minutes for most backends. If it is longer, audit container reuse and parallelism settings first.\n\n**Should I use a separate test database?**\nYes — never run integration tests against a shared dev or staging database. Testcontainers gives you an ephemeral database per run by default.\n\n**How do integration tests differ from E2E tests?**\nIntegration tests stay within the service boundary (or between two well-defined services). E2E tests drive the full stack from the user interface down. Use both; they catch different bugs.\n\n**What is contract testing and when do I need it?**\nContract testing (Pact, Schemathesis) verifies that a provider API still satisfies what consumers expect. Use it when two teams own two services that talk to each other; without it, integration breakage is discovered in staging, not CI.\n\n## Where to go next\n\n- [How to write unit tests in 2026](/blog/how-to-write-unit-tests-2026)\n- [How to set up CI/CD in 2026](/blog/how-to-set-up-ci-cd-2026)\n- [How to deploy with GitHub Actions in 2026](/blog/how-to-deploy-with-github-actions-2026)","hue":305,"metaTitle":"How to Write Integration Tests in 2026: Practical Guide","metaDescription":"Learn how to write fast, reliable integration tests in 2026. Covers Testcontainers, contract testing, CI setup, and common failure patterns to avoid.","keywords":["how to write integration tests 2026","integration testing tutorial 2026","testcontainers integration tests","integration vs unit tests 2026","backend integration testing","api integration testing 2026","database integration tests","contract testing 2026","integration test best practices","integration testing ci cd"],"tldr":["**Test boundaries, not internals** — integration tests validate that module A + module B produce the right observable outcome, not how they do it.","**Use real infrastructure where practical** — Testcontainers spins up real Postgres, Redis, or Kafka in under 10 seconds; prefer it over mocked clients.","**Keep the suite fast** — parallel execution and shared setup containers cut a 3-minute suite to under 30 seconds; developers run it on every push.","**Scope each test to one contract** — a test that touches 5 services is a system test, not an integration test; keep them separate and label them.","**Skip:** mocking the database in an integration test — you are testing the integration; swap it out and you are back to a unit test in disguise."],"readTime":"5 min read","wordCount":1092},"related":[{"slug":"integration-testing-explained-2026","title":"Integration testing explained in 2026: test the seams that unit tests miss","excerpt":"Integration tests catch the failures that happen when two correct pieces connect incorrectly. Here is the 2026 guide to writing fast, reliable integration tests.","category":"code","date":"April 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":305},{"slug":"unit-testing-explained-2026","title":"Unit testing explained in 2026: write tests that actually help","excerpt":"Most developers know they should write unit tests but struggle with what to test, how to structure them, and why their tests keep breaking. Here is the 2026 guide.","category":"code","date":"March 19, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":322},{"slug":"how-to-write-unit-tests-2026","title":"How to write unit tests in 2026: patterns, coverage, and what to actually test","excerpt":"Unit tests are fast, focused, and the foundation of a reliable test suite. This 2026 guide covers what to test, how to structure tests, and the patterns that make tests useful rather than just green.","category":"code","date":"March 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":319}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-write-better-emails-2026","title":"How to write better emails in 2026: clear, fast, and actually read","excerpt":"Most emails get skimmed for five seconds or ignored. Here is a practical 2026 system to write emails that get read, get replies, and move work forward.","category":"guides","date":"March 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":280},"older":{"slug":"how-to-write-unit-tests-2026","title":"How to write unit tests in 2026: patterns, coverage, and what to actually test","excerpt":"Unit tests are fast, focused, and the foundation of a reliable test suite. This 2026 guide covers what to test, how to structure tests, and the patterns that make tests useful rather than just green.","category":"code","date":"March 2, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":319}}