Testing

How to Mock an API Before the Backend Exists

Learn how to mock an API before the backend is ready, define a useful contract, create realistic responses, wire the frontend to mocks, and avoid contract drift.

How to Mock an API Before the Backend Exists

Frontend work often starts before the backend is finished. The screens are designed, the user journey is clear, and the team knows roughly what data needs to appear. But the actual API endpoint still returns nothing, or does not exist at all.

That does not mean the frontend has to wait.

API mocking lets you build against a simulated backend while the real one is still being designed or implemented. Instead of calling a finished service, the frontend calls a mock API that returns realistic responses with the same shape the real backend is expected to use.

The goal is not to pretend the backend is done. The goal is to agree on the contract early enough that frontend, backend, QA, and product work can move in parallel.

What Does It Mean to Mock an API?

To mock an API is to simulate the responses a real API will eventually return.

The frontend sends a normal HTTP request:

GET /api/customers/123

The mock returns a response:

{
  "id": "cus_123",
  "name": "Sarah Smith",
  "plan": "Pro",
  "status": "active"
}

From the frontend’s perspective, this looks like a real backend. The request has a URL, a method, a status code, headers, and a JSON body. The difference is that the response is generated from mock data rather than a database, queue, authentication layer, or business service.

For the general concept, see what is a mock server?. This article focuses on the practical workflow: how to set up useful mocks before the backend exists.

Start With the Contract

The most important part of API mocking is not the mock server. It is the contract.

An API contract describes how the frontend and backend agree to communicate. It should answer questions like:

  • Which endpoints exist?
  • Which HTTP methods do they use?
  • What request parameters are required?
  • What does a successful response look like?
  • What error responses can happen?
  • Which fields are optional?
  • Which values are enums?
  • What authentication or headers are expected?

A contract can be formal, such as an OpenAPI Specification, or informal, such as a Markdown document with example requests and responses. Formal contracts are usually better once more than one team or client depends on the API.

The key is to avoid building mocks from vague assumptions. If the frontend invents field names and the backend invents different ones, the mock helped nobody. It only delayed the disagreement.

Sketch the User Journey First

Before writing mock data, map the user journey.

Suppose you are building a customer dashboard. The screen might need to:

  1. Load the current customer profile
  2. Load recent invoices
  3. Load current subscription status
  4. Let the user update billing details
  5. Show errors if payment details are rejected

That journey suggests several API operations:

GET /api/customer
GET /api/invoices
GET /api/subscription
PATCH /api/billing-details

Starting from the journey keeps the mock API grounded in real product behavior. Otherwise, teams often create endpoints that look tidy on paper but do not match what the interface actually needs.

This is also where a state machine can help. If the UI moves between loading, ready, saving, failed, and complete, the mock should support those states instead of only returning a perfect success response.

Define the Happy Path Response

Begin with the successful response the frontend needs most.

For example:

GET /api/subscription
{
  "id": "sub_123",
  "plan": "Pro",
  "status": "active",
  "renewalDate": "2026-09-01",
  "seats": {
    "used": 8,
    "included": 10
  }
}

This response should be realistic enough that the UI can be built honestly. Include nested objects, empty states, dates, enum values, limits, and anything else that will affect layout or logic.

Avoid placeholder-only mocks like this:

{
  "name": "Test",
  "value": "Test"
}

That kind of response unblocks a fetch call but does not prove the interface can handle real data.

Add Empty States Early

Many bugs hide in empty states because teams only mock full data.

A dashboard with invoices should handle:

{
  "items": [],
  "total": 0
}

A search endpoint should handle:

{
  "query": "zzzzzz",
  "results": []
}

An onboarding flow should handle a user who has not configured anything yet.

Mocking empty states early forces the UI to answer basic product questions. Should the screen show a message? A setup button? A blank table? A disabled export action? These decisions are easier before the real backend exists because nobody has to migrate real behavior yet.

Mock Error Responses Too

Successful responses are only half the contract.

Your mock API should also return failures such as:

  • 400 Bad Request for invalid input
  • 401 Unauthorized when the user is not logged in
  • 403 Forbidden when the user lacks permission
  • 404 Not Found for missing resources
  • 409 Conflict for state conflicts
  • 422 Unprocessable Entity for validation errors
  • 429 Too Many Requests for rate limiting
  • 500 Internal Server Error for unexpected backend failures

For example:

PATCH /api/billing-details
{
  "error": {
    "code": "card_declined",
    "message": "The card was declined.",
    "field": "cardNumber"
  }
}

Testing these responses before the backend exists is valuable because real systems rarely fail on command. A mock lets you deliberately build and test the unhappy paths.

Choose Where the Mock Should Live

There are several ways to mock an API, and the best choice depends on who needs to use it.

In the Browser

Browser-level mocking intercepts requests from the frontend during local development or tests. Mock Service Worker is a common choice because it can intercept network requests in the browser and in Node.js test environments.

This works well when:

  • The frontend team owns the mock behavior
  • You want mocks close to UI tests
  • You need realistic fetch or XMLHttpRequest behavior
  • You want to avoid changing application code for tests

As a Local Mock Server

A local mock server runs on your machine and exposes HTTP endpoints.

This works well when:

  • Multiple apps need to call the same mock
  • Mobile apps or external clients need an HTTP URL
  • The frontend should behave as if it is talking to a separate backend
  • You want mocks generated from an API specification

Tools like Prism can generate mock servers from OpenAPI documents, which is useful when the contract is the source of truth.

As a Shared Hosted Mock

A hosted mock API gives everyone the same URL.

This works well when:

  • Designers, QA, frontend, backend, and external partners all need access
  • You want demos before the backend exists
  • You need a stable environment for review builds
  • The team is distributed

The trade-off is governance. A shared mock must be versioned and maintained carefully, or it becomes another drifting dependency.

Use OpenAPI When the Contract Matters

If the API is more than a quick prototype, consider writing an OpenAPI document.

OpenAPI can describe:

  • Paths and methods
  • Request bodies
  • Query parameters
  • Response schemas
  • Status codes
  • Headers
  • Authentication schemes
  • Example responses

That same document can often power documentation, generated types, mock servers, request validation, and contract tests. This is why API-first teams often start with the specification before backend code exists.

For example, a tiny OpenAPI fragment might define:

paths:
  /api/subscription:
    get:
      responses:
        "200":
          description: Current subscription
          content:
            application/json:
              schema:
                type: object
                required: [id, plan, status]
                properties:
                  id:
                    type: string
                  plan:
                    type: string
                  status:
                    type: string
                    enum: [active, trialing, past_due, cancelled]

This is more useful than a random JSON file because it describes the rules, not just one example.

The shape of API data also overlaps with JSON Schema vs TypeScript types. TypeScript helps while coding. Runtime schemas and API specifications help validate data crossing the network.

Build a Small Mock First

Do not try to mock the entire product on day one.

Start with one thin vertical slice:

One screen
One user journey
One success response
One empty response
One validation error
One server error

That gives the frontend enough realism to move while keeping the mock easy to change. Early API design is fluid. If the team discovers that a field should be renamed or split into a nested object, changing one slice is cheap.

Once the contract stabilizes, expand the mock to cover more endpoints and scenarios.

Keep Mock Data Realistic

Mock data should resemble production data closely enough to expose UI and logic problems.

Use realistic:

  • Names
  • Dates
  • IDs
  • Amounts
  • Long strings
  • Empty arrays
  • Large arrays
  • Optional fields
  • Permission differences
  • Slow responses
  • Error messages

Avoid always returning the same perfectly balanced example. Real data is uneven. Names wrap. Tables overflow. Amounts vary. Dates expire. Some users have no data. Some users have too much data.

For frontend quality, realistic mock data matters as much as realistic CSS test cases. A UI that only works with perfect mock data is not ready for production.

Simulate Latency

Fast mocks can hide loading bugs.

If every mock response returns instantly, the UI may never properly exercise spinners, skeleton screens, disabled buttons, optimistic updates, cancellation behavior, or duplicate submissions.

Add controllable delays:

GET /api/customer -> 200 after 800 ms
PATCH /api/billing-details -> 422 after 1200 ms

This is especially helpful for testing browser workflows with tools like Playwright, where timing and async state often determine whether the user experience is actually reliable.

Version the Mock With the Code

Mock definitions should live close to the application or contract they support.

Good options include:

  • mocks/
  • fixtures/
  • src/mocks/
  • openapi/
  • contracts/

The exact folder matters less than the habit: commit mocks to version control, review them in pull requests, and update them when the contract changes.

If mock data lives only in someone’s local tool or personal workspace, the team will eventually lose track of what the frontend was built against.

Wire the Frontend Through Configuration

The frontend should be able to switch between mock and real API environments without code edits.

For example:

API_BASE_URL=http://localhost:4010

Local development can point to the mock server. A staging build can point to a real backend. Tests can point to either a local mock or test service.

Avoid hardcoding mock URLs inside application logic. Configuration keeps the app honest: it still makes real HTTP calls, just to a mock endpoint.

This is especially useful when working with third-party APIs that have rate limits or credentials, such as the Instagram Insights API. During development, the app can call mocks until the integration needs real validation.

Test the Mock Contract

Mocks can drift away from the backend if nobody checks them.

There are several ways to reduce drift:

  • Generate mocks from OpenAPI instead of hand-writing every response
  • Validate mock responses against a schema
  • Add contract tests between consumers and providers
  • Run frontend tests against both mock and staging environments
  • Review API changes with frontend and backend together
  • Keep example responses in the same repository as the contract

This is where contract testing vs integration testing becomes important. Mocking helps teams move early. Contract testing helps ensure the mock and real service do not quietly become different APIs.

Do Not Mock Away Product Decisions

Mocks should expose uncertainty, not hide it.

If the team does not know whether a user can have multiple subscriptions, do not quietly mock only one. If the backend might paginate results, decide that early. If permissions affect which actions are available, represent those permissions in the mock.

A good mock often creates useful conversations:

  • Should this field be nullable?
  • What happens when the list is empty?
  • Can this operation fail after partial success?
  • Does the client need a machine-readable error code?
  • Is the status a string enum or derived from several fields?

Those conversations are part of the value. API mocking is not just a testing trick. It is an API design tool.

Know When to Switch to the Real Backend

Mocks are temporary scaffolding for parts of the workflow.

You should start using the real backend when:

  • Authentication behavior matters
  • Database queries affect response shape or timing
  • Permissions are implemented
  • Business rules are complex
  • The API is stable enough for integration testing
  • Release confidence depends on real infrastructure

Mocking should not replace integration tests, staging environments, or end-to-end validation against real services. A feature that works perfectly against mocks can still fail because of CORS, authentication, serialization, database constraints, infrastructure routing, or subtle backend validation rules.

For requests that travel through multiple services, tools like distributed tracing and correlation IDs vs trace IDs become important once the real backend exists. A mock cannot show you what happened across services because those services were never called.

A Practical Mocking Workflow

A reliable workflow looks like this:

  1. Describe the user journey
  2. Draft the endpoint contract
  3. Add success, empty, and error responses
  4. Choose a mock approach: browser mock, local server, or hosted mock
  5. Wire the frontend through API_BASE_URL
  6. Build the UI against the mock
  7. Review the contract with backend developers
  8. Add contract checks or schema validation
  9. Run key tests against the mock for speed
  10. Run final validation against the real backend

That sequence lets the frontend move early without pretending the real integration is done.

Common Mistakes

Only mocking the happy path. Success responses are not enough. Empty states, validation errors, authorization failures, and server failures should be part of the mock.

Inventing the contract alone. Frontend-only mocks can drift from backend reality. The contract should be reviewed by the people implementing the API.

Using unrealistic data. Perfect mock data hides layout, validation, and edge-case bugs.

Hardcoding mock behavior into the app. The application should call an API through configuration, not branch everywhere because it is in mock mode.

Treating mocks as proof the integration works. Mocks prove the frontend can handle the expected contract. They do not prove the real backend works.

Letting mock definitions go stale. Stale mocks are worse than no mocks because they create false confidence.

If you are building or testing APIs, try these topics:

For external references, start with the OpenAPI Specification, Mock Service Worker documentation, and Prism for OpenAPI-driven mock servers.

Frequently Asked Questions

Can I mock an API before the backend exists? Yes. That is one of the main reasons API mocking exists. You define the expected contract, create realistic responses, and point the frontend at a mock server or request interceptor until the real backend is ready.

Should I use OpenAPI for mocking? Use OpenAPI when the API contract matters across teams, clients, or services. It gives you a shared source of truth that can power documentation, mock servers, validation, and contract tests.

Is API mocking the same as integration testing? No. API mocking simulates a backend so development and tests can run early or predictably. Integration testing verifies that real systems work together. You usually need both.

What should I mock first? Start with one user journey. Add the main success response, an empty state, a validation error, and a server error. Expand from there as the contract becomes clearer.

How do I stop mocks from becoming outdated? Keep mocks versioned, generate them from a contract when possible, validate responses against schemas, review API changes across frontend and backend, and add contract tests once the real backend exists.

Conclusion

Mocking an API before the backend exists lets teams build and test sooner, but the useful part is not the fake data. The useful part is agreeing on the contract early.

Start with the user journey, define the endpoints and response shapes, include empty and error states, and choose a mock approach that fits the team. Keep the mock versioned, realistic, and tied to the API contract so it remains a useful development tool rather than a source of false confidence.

The best API mocks are temporary, honest, and specific. They help the frontend move now while making the real backend integration smoother later.

Written by the Workshelve team, who write practical explainers on data integrity, networking, and developer tooling.