Mocking a service you don't control
The case is always the same: your flow goes through a service that is not yours, and you cannot reach it. You have no VPN today, it is down, another team maintains it, or it does not exist yet. A mock unblocks the afternoon — and it is also the easiest way to spend a week testing against a lie.
First: is a mock what you need?
The deciding question is not "can I reach that service?", but "am I testing its response or passing through it?".
| Situation | What to use | Why |
|---|---|---|
| You need it to return something so your flow continues | Mock | Its content is not what you verify; it is a precondition |
| You are checking how your code reacts to what it returns | The real service | A stub will tell you all is well, and you will not know |
| You are chasing a bug that only shows against the real thing | The shared environment | The bug lives in the difference between the stub and the original |
| It is an integration test | Testcontainers or a double in the test | The lifecycle is governed by the test, not by you |
| The service does not exist yet | Mock from its contract | This is the case where the mock cannot lie: it is the only source |
The risk nobody mentions: the stub stands still
A mock freezes a contract. The real service keeps changing and your stub never hears about it: new fields you do not return, an error code that now means something else, a date format that changed. Your code passes locally and fails on integration, which is exactly the problem the mock promised to avoid.
Two things reduce it a lot, and neither of them is "be careful":
- Generate the stub from the contract, not from memory. If there is an OpenAPI, the response skeleton comes from there and gets regenerated when the contract changes.
- Capture real calls on the day you do have access, and turn them into stubs. A captured example invents nothing.
The four ways to set it up
| Way | Cost of entry | Where it breaks |
|---|---|---|
| A stub server (WireMock, MockServer) in your compose | One more container and its mapping files | You still have to redirect the caller's URL to it, in the configuration of somebody else's repository |
| A fake service written by hand | Half an hour, and done | It becomes a project nobody maintains and nobody knows the owner of |
| Intercepting in the service's own HTTP client | Little, if the framework allows it | It is fake code inside your repository, and somebody will commit it |
| A mock built into the orchestrator | Installing the tool | It ties the local environment to that tool |
Notice that three of the four share the same pain point, and it is not writing the stub: it is getting the caller to go to the stub. That means changing a URL in the configuration of a repository that is often not yours, and undoing it when you want the real service back.
How Aseptic does it
Aseptic treats "where does this dependency call" as a first-class decision, not as a file
edit. For each dependency you choose between three destinations —the local service, your
cloud environment or a mock— and the engine rewrites the URL on start-up,
injecting it as a -D property or an environment variable.
Practical consequences:
- The repository is not touched. Nothing to commit, nothing to revert, nothing to review in a pull request.
- Going back is changing the destination, not undoing an edit. You can go back and forth between mock and real service several times in one afternoon.
- The rest of the scenario finds out on its own: the decision is taken once and everyone calling that service applies it.
Stubs are defined in the Mock Studio: capture a call and turn it into a stub in one click, generate the response skeleton from an OpenAPI, and organise variants per service —the happy response, the one that returns 404, the slow one— with hot reload, without restarting anything.
What a mock will not give you
Worth saying here, because this is where time gets lost: if the bug you are chasing lives in the real service —a header the gateway adds, a payload nobody documented, the behaviour under load— the mock will not reproduce it by definition. What you need there is to reach the shared environment, and if your system is too big for your machine, the Telepresence comparison explains the other approach.
The concrete steps are in mocking a dependency, and if what blocks you is having to bring up too many things, the page on running only part of the system.
Frequently asked questions
When should I mock a service and when should I not?
Mock it when its response is not what you are testing: you need it so your flow can proceed, not to verify it. Do not mock it when what you want to check is precisely how your code reacts to what that service really returns —there a stub will tell you everything is fine and it will not be true—.
Won't a mock end up lying to me?
It can, and that is its real risk: the stub freezes a contract that keeps changing on the other side. You mitigate it by generating it from the service's OpenAPI rather than by hand, and by capturing real calls whenever you can reach it.
How do I switch between the real service and the mock without editing my repositories?
With Aseptic it is a per-dependency decision: you pick local, cloud or mock and the URL is rewritten on start-up, injected as configuration. The repository file stays exactly as it is in git.
Do I have to write the stub by hand?
Not necessarily. You can generate the response skeleton from an OpenAPI, or capture a real call the one time you had access and turn it into a stub in one click.