Mocking flaky third-party APIs in your CI pipeline
Use Mockra ephemeral namespaces to spin up isolated mock environments for every CI run. No shared state, no flaky third-party dependencies, deterministic test results.
The Problem
Your integration test suite calls three third-party APIs: Stripe for payments, SendGrid for emails, and an internal microservice that doesn't have a stable staging environment. The suite passes locally. In CI, it fails about 30% of the time — sometimes it's a Stripe sandbox timeout, sometimes it's the internal service returning a 503 during a deployment window, sometimes it's unexplained.
You've seen teams handle this in two ways: mark flaky tests as "skip" (and never fix them) or add retry logic that makes a 2-minute test suite take 8 minutes. Neither solves the root problem.
The root problem is that your tests have a runtime dependency on APIs you don't control and can't make deterministic.
This tutorial shows how to remove that dependency entirely using Mockra ephemeral namespaces — isolated mock environments created fresh for each CI run and destroyed when it's done.
How Ephemeral Namespaces Work
Mockra's ephemeral namespace API lets you programmatically create and destroy isolated mock environments. Each namespace is a unique URL prefix (mock.mockra.io/your-namespace/) where all your configured mock endpoints are available. Ephemeral namespaces start blank — you upload an OpenAPI schema and configure mock responses before your tests run, or point your tests at a pre-configured persistent namespace.
The workflow:
- CI starts → create ephemeral namespace → get namespace slug
- Upload your OpenAPI schema to the namespace (or use a pre-configured one)
- Run your test suite, pointing it at
mock.mockra.io/your-namespace/ - CI ends → destroy the namespace
Prerequisites
- A Mockra account with a Pro plan (ephemeral namespaces are a Pro feature)
- A Mockra API key created in the dashboard under your workspace's API Keys section
- GitHub Actions (the tutorial uses GitHub Actions, but the API calls work in any CI system)
Step 1: Create a Mockra API Key
- Go to dash.mockra.io → your workspace → API Keys section
- Create a key named
ci-integration-tests - Copy the key (
mra_...) — it is shown once only - Add it to your GitHub repository as a secret: Settings → Secrets and variables → Actions → New repository secret
- Name:
MOCKRA_API_KEY - Value: the
mra_...key
- Name:
Step 2: The GitHub Actions Workflow
# .github/workflows/integration-tests.yml
name: Integration Tests
on:
pull_request:
push:
branches: [main]
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Mockra ephemeral namespace
id: mockra
run: |
RESPONSE=$(curl -s -X POST https://api.mockra.io/namespaces \
-H "Authorization: Bearer ${{ secrets.MOCKRA_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"name": "ci-pr-${{ github.run_id }}"}')
NAMESPACE=$(echo $RESPONSE | jq -r '.namespace.namespace')
MOCK_BASE=$(echo $RESPONSE | jq -r '.namespace.mock_url_base')
echo "namespace=$NAMESPACE" >> $GITHUB_OUTPUT
echo "mock_base=$MOCK_BASE" >> $GITHUB_OUTPUT
echo "Created namespace: $NAMESPACE"
echo "Mock base URL: $MOCK_BASE"
- name: Upload OpenAPI schema to namespace
run: |
# Upload your spec — Mockra auto-generates mock responses for every endpoint
curl -s -X POST \
"https://api.mockra.io/workspaces/YOUR_WORKSPACE_ID/schemas" \
-H "Authorization: Bearer ${{ secrets.MOCKRA_API_KEY }}" \
-F "file=@path/to/your-api-spec.yaml"
- name: Run integration tests
env:
MOCK_BASE_URL: ${{ steps.mockra.outputs.mock_base }}
run: |
# Your test command — tests read MOCK_BASE_URL from the environment
npm test -- --testPathPattern=integration
- name: Destroy Mockra namespace
if: always()
run: |
curl -s -X DELETE \
"https://api.mockra.io/namespaces/${{ steps.mockra.outputs.namespace }}" \
-H "Authorization: Bearer ${{ secrets.MOCKRA_API_KEY }}"
echo "Namespace ${{ steps.mockra.outputs.namespace }} destroyed"
Key design decisions worth calling out:
if: always()on the destroy step — cleanup runs even when tests fail. No leaked namespaces.github.run_idmakes the namespace name unique per CI run — no conflicts on parallel PRs.MOCK_BASE_URLenv var — the test runner reads the namespace URL from the environment. This is the same pattern used for staging URLs, database connection strings, and any other per-environment config.jqparses the JSON response — it's available by default onubuntu-latest.
Step 3: Update Your Tests to Read the Mock URL
// tests/integration/checkout.test.js
// Read mock base URL from environment — falls back to a default for local dev
const MOCK_BASE = process.env.MOCK_BASE_URL ?? 'https://mock.mockra.io/local-dev';
describe('Checkout flow', () => {
it('creates a payment intent', async () => {
const response = await fetch(`${MOCK_BASE}/v1/payment_intents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: 2000, currency: 'usd' }),
});
expect(response.status).toBe(200);
const data = await response.json();
expect(data.id).toMatch(/^pi_/);
});
});
The local development fallback lets developers run integration tests locally against a persistent namespace. CI runs against a fresh ephemeral one. The test code is identical in both cases.
Step 4: Handling Parallel CI Runs
github.run_id ensures uniqueness — two concurrent PRs each get their own namespace, no shared state. Matrix builds work the same way:
- name: Create Mockra ephemeral namespace
id: mockra
run: |
RESPONSE=$(curl -s -X POST https://api.mockra.io/namespaces \
-H "Authorization: Bearer ${{ secrets.MOCKRA_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"name": "ci-${{ github.run_id }}-${{ matrix.node-version }}"}')
NAMESPACE=$(echo $RESPONSE | jq -r '.namespace.namespace')
MOCK_BASE=$(echo $RESPONSE | jq -r '.namespace.mock_url_base')
echo "namespace=$NAMESPACE" >> $GITHUB_OUTPUT
echo "mock_base=$MOCK_BASE" >> $GITHUB_OUTPUT
Testing Failure Scenarios in CI
This is where mocking genuinely earns its keep. Configure mock endpoints to return error responses, then run a separate test suite that verifies your app handles downstream failures correctly.
In the dashboard, set POST /v1/payment_intents to return a 503:
{
"error": {
"message": "Service temporarily unavailable"
}
}
Then in your workflow:
- name: Run resilience tests
env:
MOCK_BASE_URL: ${{ steps.mockra.outputs.mock_base }}
TEST_SCENARIO: payment_service_down
run: npm test -- --testPathPattern=resilience
Test how your checkout page handles a Stripe outage — deterministically, in CI, before it happens in production.
GitLab CI Equivalent
The same pattern in GitLab CI syntax:
# .gitlab-ci.yml
integration_tests:
stage: test
script:
- |
RESPONSE=$(curl -s -X POST https://api.mockra.io/namespaces \
-H "Authorization: Bearer $MOCKRA_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"name\": \"ci-${CI_PIPELINE_ID}\"}")
NAMESPACE=$(echo $RESPONSE | jq -r '.namespace.namespace')
MOCK_BASE=$(echo $RESPONSE | jq -r '.namespace.mock_url_base')
- MOCK_BASE_URL=$MOCK_BASE npm test -- --testPathPattern=integration
- curl -s -X DELETE "https://api.mockra.io/namespaces/${NAMESPACE}" -H "Authorization: Bearer $MOCKRA_API_KEY"
Wrap-Up
Every CI run now gets its own isolated mock environment. Tests that were flaky because of third-party API dependencies are now deterministic. The namespace is cleaned up automatically — no infrastructure to manage.
Ephemeral namespaces are available on Mockra Pro. If you're evaluating, the free tier lets you test with a persistent namespace — the mock endpoint behavior is identical.
Try Mockra free
5,000 requests/month, no credit card required. Upload an OpenAPI spec and get mock endpoints in under 60 seconds.