Where API Tests Belong in a CI/CD Pipeline
One of the most common architectural mistakes I see in test suites isn't bad test code — it's good test code running at the wrong moment. Teams write solid API tests, wire them into CI/CD, and then wonder why their pipeline takes 25 minutes or why a broken contract slips through to staging. The problem usually isn't the tests themselves. It's placement. Where a test runs in the pipeline determines whether it acts as a fast feedback signal or a slow, expensive afterthought.
The CI/CD pipeline isn't a single slot you dump all tests into. It's a sequence of gates, and each gate has a job. Unit tests belong early because they're fast and self-contained. End-to-end tests belong late because they require a fully assembled environment. API tests are interesting because they span a wide spectrum — some behave like unit tests, some behave like integration tests, and some are closer to end-to-end smoke checks. Treating them as a monolith and running them all in one stage is where things go wrong.
In this article I'll walk through how I think about placing API tests across pipeline stages: which tests earn a spot in the fast pre-merge gate, which ones belong in a post-deploy verification step, and what to do with the slower contract and scenario-based tests in between. Every recommendation here is something I've applied on real pipelines — not theory.
Learn Node.js, Cucumber, GitHub Copilot, APIs, CI/CD, and modern automation by building a complete framework.
The Pre-Merge Gate: Which API Tests Earn a Spot Here
The pre-merge gate — the check that runs on every pull request before code lands in main — has one hard constraint: it must be fast. Developers won't wait eight minutes for a PR check without eventually finding ways to skip it or merge anyway. That means you need to be ruthless about what you put here.
For API tests, the candidates for the pre-merge gate are tests that meet all three of these criteria:
- They don't need a live external dependency. Tests that hit a real database, a real third-party service, or a real message broker don't belong here. Use mocks, stubs, or a lightweight in-process server instead.
- They validate contract and schema, not full user journeys. Checking that a
POST /ordersendpoint returns a201with the correct response shape is a pre-merge test. Walking through a five-step checkout flow is not. - They run in under 60–90 seconds total. If your full pre-merge API suite exceeds that, you have too many tests in this stage — or they're doing too much I/O.
A practical pattern I return to often: keep a @smoke or @fast tag on tests that meet these criteria and configure CI to run only that tagged subset on PRs. In pytest this looks like:
# pytest.ini or pyproject.toml
[pytest]
markers =
smoke: fast, dependency-free tests safe for pre-merge
# CI command
pytest -m smoke --tb=short
In Behave you'd use tags the same way — @smoke on the scenario, --tags=smoke in the CI command. The tagging discipline is the hard part; the tooling is straightforward.
One thing worth noting: if you're deciding between BDD and plain pytest for your API tests, that choice affects how you structure and tag scenarios for pipeline stages. BDD feature files with scenario-level tags give you very fine-grained control over what runs where — which is an underrated argument for BDD in teams with complex CI pipelines.
What you're not doing in the pre-merge gate: full integration tests, load tests, contract validation against a live provider, or any test that requires a deployed environment. Those have their own homes downstream.
Post-Deploy Verification: API Tests as a Deployment Health Check
Once a build has been deployed to a staging or test environment, you have a new question to answer: did the deployment actually work? This is where a second wave of API tests earns its place — not as a quality gate in the same sense as pre-merge, but as a deployment verification step. The goal is to answer "is this environment healthy and does the API behave correctly against real infrastructure?" before you promote the build further or let testers loose on it.
These tests look different from pre-merge tests in a few key ways:
- They run against a real deployed instance, not mocks. That means you're catching configuration issues, environment variable mismatches, and infrastructure problems that mocks will never surface.
- They cover happy-path critical flows end-to-end — authentication, core CRUD operations, any integration point that the application depends on to function at all.
- They're allowed to be slower, but should still be time-boxed. I typically aim for under five minutes for a post-deploy verification suite. If it takes longer, it's doing too much — save the deeper scenarios for a scheduled nightly run.
A pattern that works well here is a dedicated verify or smoke-deploy test suite that's separate from your main test suite at the file level, not just by tag. This makes it easy to invoke from a deployment script without accidentally running the wrong set:
# Triggered by deployment pipeline, not by PR
pytest tests/verify/ --base-url=$STAGING_URL --tb=long -v
Passing a --base-url via command-line or environment variable is important here — your verification suite should be environment-agnostic by design, pointing at staging today and production tomorrow with a single config change.
Failures at this stage should block promotion. If your post-deploy verification fails, you don't move the build to the next environment. That's the gate. I've seen teams treat post-deploy test failures as "informational" — a notification that gets ignored while the build promotes anyway. That defeats the entire purpose. Wire the pipeline so a non-zero exit code from pytest stops the promotion step cold.
This is also the right stage to surface structured logging in your test output. When a post-deploy test fails in CI, you often can't attach a debugger or re-run interactively. Rich request/response logging captured in the test output — status codes, headers, response bodies — is what lets you diagnose the failure from a pipeline artifact without having to reproduce it locally.
Contract Tests and Scenario Suites: Fitting the Slower Tests Into the Pipeline
Not every API test belongs in a fast gate. Some of the most valuable tests you can write are also the slowest to run — and forcing them into a pre-merge or post-deploy slot just means they get disabled when the pipeline gets slow. The solution isn't to skip them; it's to give them the right slot.
Contract tests are the clearest example. Contract testing catches breaking API changes that schema validation alone won't surface — things like a field being renamed, a required property becoming optional, or a new enum value appearing that consumers don't handle. These tests are incredibly valuable, but they often require a running provider and sometimes a contract broker. They don't belong in a 90-second pre-merge gate. Instead, run them on a schedule (nightly or on every merge to main) and as a mandatory gate before any release promotion to production.
Scenario-based integration tests — multi-step flows that exercise real business logic across several endpoints — belong in a similar slot. Run them post-merge on main, or as part of a release candidate pipeline, not on every PR. The signal they provide is about system behavior over time, not about whether this specific commit broke something obvious.
Here's how I typically organize this across pipeline stages:
| Stage | Test Types | Trigger | Max Time Budget |
|---|---|---|---|
| Pre-merge (PR check) | Schema, unit-level API, mocked integration | Every PR | ~90 seconds |
| Post-deploy (staging) | Happy-path smoke, critical flows, real infra | Every deploy | ~5 minutes |
| Scheduled / release gate | Contract tests, full scenario suites, regression | Nightly or pre-release | 15–30 minutes |
The discipline here is resisting the urge to collapse all three stages into one. Teams do this when they're short on time or when the pipeline tooling feels complicated to configure for multiple stages. But the consequence is always the same: the suite gets slow, developers start ignoring failures, and the tests stop providing signal. Three stages with clear ownership is more maintainable than one giant stage with everything in it.
One last thing: pipeline placement only works if your tests are reliable. Flaky tests in a deployment gate are worse than no tests — they erode trust until the team starts bypassing the gate entirely. Invest in stability (deterministic data setup, idempotent teardown, retry logic only where genuinely appropriate) before you invest in adding more tests. A small, reliable suite at the right pipeline stage beats a large, flaky one running everywhere.