DEV Community

Cover image for Your Spec Already Has Mock Data
Emmanuel King Kasulani
Emmanuel King Kasulani

Posted on

Your Spec Already Has Mock Data

Every new project starts the same way. The API contract is agreed, the spec is written, and now the frontend team needs something to build against. They shouldn't have to set up mock servers with config files just to start working. So I do what every backend engineer does — I scaffold the service, wire up every endpoint with hardcoded JSON responses, deploy it to staging, and tell the frontend team to point their app at it.

It works. Frontend builds against staging. Responses are predictable, the contract is fresh, and nobody had to configure anything. The dummy endpoints are throwaway code — I would have preferred to jump straight into real implementation after designing the spec — but it's a small tax to unblock the team.

Then the real implementation starts replacing the dummy endpoints. One by one, hardcoded responses become live database queries. And this is where things unravel.

The responses that were deterministic — {"name": "Fido"} every single time — now depend on database state, test data, and the order of previous requests. Frontend's assertions break. Their UI tests become flaky. The predictable surface they were building against quietly disappears underneath them.

So they do what they avoided at the start: they set up their own mock server. WireMock stub mappings. Prism config. Postman saved responses. Config files that describe the API in a parallel artifact, separate from the spec.

By this point, the spec has evolved through several sprints. The mock configs are stale on day one. And now drift begins — two descriptions of the same API that silently disagree, with nobody maintaining the sync.

One problem created the other. The scaffold was a workaround to avoid config-heavy mocks. But it was temporary by design. When it dissolved, the config-heavy mocks became inevitable — and by then, the spec had moved far enough that the configs were already wrong.

The root cause

After hitting this cycle enough times, I noticed the common thread: every approach we tried kept mock data somewhere other than the spec.

Hardcoded responses lived in scaffold code. Stub mappings lived in config files. Saved responses lived in Postman collections. Each was a parallel artifact duplicating information already in the OpenAPI spec — schemas, status codes, example values — and each drifted from it the moment the API evolved.

The gap between the spec and the mock grows with every sprint. It doesn't matter which tool you pick. If the mock data lives in a separate artifact, the question isn't whether it'll disagree with the spec. It's when.

The insight hiding in plain sight

OpenAPI has supported example values on properties since version 3.0. Most specs already use them — for documentation rendering, for code generation, for Swagger UI. These aren't decorative. They're structured, schema-adjacent data authored by backend engineers who design the API.

components:
  schemas:
    Pet:
      type: object
      properties:
        name:
          type: string
          example: "Fido"
        status:
          type: string
          enum: [available, pending, adopted]
        email:
          type: string
          format: email
Enter fullscreen mode Exit fullscreen mode

That fragment contains three mock values: an authored example ("Fido"), a constrained set (available, pending, adopted), and a format hint (email). Combined with the schema's types, constraints, and structure, the spec has everything a mock server needs to produce realistic responses.

Yet every mock tool ignores this and asks you to define mock data somewhere else.

So I built a tool that doesn't

I built Mimikos to treat the OpenAPI spec as the only input. No config files. No scaffold services. No separate mock data to maintain.

mimikos start petshop.yaml
Enter fullscreen mode Exit fullscreen mode
🎭 mimikos 0.3.9
Spec: PetShop Pro (OpenAPI 3.0.3)
Operations: 27 endpoints classified

  METHOD  PATH                                  BEHAVIOR   CONFIDENCE
  GET     /pets                                 → list        high
  POST    /pets                                 → create      high
  GET     /pets/{petId}                         → fetch       high
  PATCH   /pets/{petId}                         → update      high
  DELETE  /pets/{petId}                         → delete      high
  GET     /store/orders                         → list        high
  GET     /clinics/{clinicId}/rooms             → list        high
  GET     /me                                   → fetch       high
  ...

Listening on :8080 (deterministic mode, strict=false)
Enter fullscreen mode Exit fullscreen mode

It reads the spec, classifies every endpoint automatically, and serves responses generated from the schemas. Authored example values are used directly. Fields named email get email addresses. Fields with format: date-time get timestamps.

curl http://localhost:8080/pets/42
Enter fullscreen mode Exit fullscreen mode
{
  "id": "d1b2f3a4-5678-9abc-def0-1234567890ab",
  "name": "Fido",
  "status": "available",
  "age": 3,
  "email": "alice.johnson@example.com",
  "created_at": "2024-08-15T10:30:00Z"
}
Enter fullscreen mode Exit fullscreen mode

"Fido" comes from the spec's example. "available" from the enum. The UUID, email, and timestamp are generated from format and field-name awareness. No configuration step between "I have a spec" and "I have a working mock."

And crucially: the same request always returns the same response. Deterministic by default — the property that staging lost the moment real implementations replaced the dummy endpoints.

Why it doesn't drift

One input means nothing to sync. When the spec changes, responses change. There's no parallel artifact to update.

Two design choices keep it stable over time:

Per-field sub-seeding — adding or removing a field in the schema doesn't change existing field values. Only the affected field changes. Schema evolution doesn't cascade into unrelated snapshot failures.

Spec examples take priority — if you authored an example value, Mimikos uses it. It only generates data for fields that don't have explicit values. You control exactly how much is authored vs. generated.

What it won't do

Mimikos mocks the API contract, not business logic. It won't simulate authentication, enforce rate limits, or replicate stateful workflows in deterministic mode. These are deliberate boundaries — a mock that replicates business logic is just another implementation that drifts in more dangerous ways.

For CRUD testing, there's a stateful mode (--mode stateful) where POST creates resources and GET retrieves them. But the default is pure deterministic generation — same request in, same response out.

Try it

go install github.com/mimikos-io/mimikos/cmd/mimikos@latest
Enter fullscreen mode Exit fullscreen mode

Or download a pre-built binary from GitHub Releases.

Grab the demo spec to explore the full feature set:

curl -o petshop.yaml https://raw.githubusercontent.com/mimikos-io/mimikos/main/testdata/specs/petshop.yaml
mimikos start petshop.yaml
Enter fullscreen mode Exit fullscreen mode

Documentation: mimikos.dev
Source: github.com/mimikos-io/mimikos


Your spec already has mock data. You just need something that uses it.

Top comments (0)