mock-rest-apis · git:20260821.ff81a24 · 2026-08-21 · sha256 0977dd0c400e2a92

mock-rest-apis git:20260821.ff81a24B

Immutable. This exact content is served forever at /api/v1/blob/0977dd0c400e2a92.

---
name: mock-rest-apis
description: "Stand up an HTTP endpoint you control instead of a live third-party API, and point the Mendix app at it — Prism from an OpenAPI contract, a constant swap, or a forward proxy. Use when building or debugging a REST integration without the real API, forcing a 404/500 through an error handler, or running offline or in CI."
---

# Mock REST APIs Skill

Use this skill when a REST integration needs an endpoint you control instead of a
live third-party API — while building it, while reproducing a bug, or while
verifying the app in a browser or a test run.

Developing against the real API means network, rate limits, credentials, and a
payload that can change under you. None of that is where Mendix integration
defects live: those are in the mapping, the entity types, the error handler, and
the BSON. A mock removes the variables that are not the bug.

## When to Use This Skill

- Building a REST client or `REST CALL` microflow before (or without) real credentials
- Reproducing a payload-shaped bug **deterministically** — a shape small enough to read, that behaves the same on every run
- Exercising error paths: 404, 500, a timeout, a 401 from missing auth
- Verifying the app (`test-app`) or running a suite (`test-microflows`) offline or in CI
- Redirecting the outbound calls of an app whose model you must not edit

## Two separate problems

Almost every wasted hour here comes from conflating them:

| Problem | Answer |
|---|---|
| **Something must answer the request** | A mock server: Prism (from a contract), WireMock, mitmproxy |
| **The app must send the request there** | A constant, a `BaseUrl`, or a forward proxy — see below |

A mock server is **not** an interceptor. Prism serves one contract at one port
and answers only clients that address it. Asking it to "catch all calls the app
already makes" is a category error — that is the forward-proxy job, further down.

## 1. Point the app at the mock

Three routes, cheapest first. Pick by how the URL is built.

### The URL is built in the microflow — use a constant, change nothing per run

A `REST CALL` URL is an **expression**, so it can be assembled from a constant
(`@Module.Constant` is Mendix's constant reference — `$Name` is a *variable*):

```sql
create constant MyModule.ApiBaseUrl type String default 'https://api.example.com/v1';

create microflow MyModule.CallApi() returns string
begin
  $response = rest call get @MyModule.ApiBaseUrl + '/rates'
    header Accept = 'application/json'
    returns string;
  return $response;
end;
```

Then swap the endpoint per run, with no model change and nothing committed:

```bash
# this run only — never written to the project
mxcli run --local -p app.mpr --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020

# a test suite against the mock (--constant needs --local)
mxcli test tests/ -p app.mpr --local --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020

# machine-local default, gitignored: every run picks it up
mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr

# flip it on an app that is already running
mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr --apply
```

`constant set` refuses a name the project does not define, so a typo cannot
silently apply to nothing. `mxcli constant list -p app.mpr` shows the winning
value for every constant **and which layer set it** — read it first whenever a
run does not use the endpoint you expected.

### The call goes through a REST client document — rewrite `BaseUrl`

A REST client document's `BaseUrl` is a **literal**; it cannot reference a
constant. Point it at the mock by re-running the create, which is a one-line diff:

```sql
create or modify rest client MyModule.RatesAPI (
  OpenAPI: 'specs/rates.json',
  BaseUrl: 'http://127.0.0.1:4020'
);
```

`BaseUrl` also overrides `servers[0].url` at import time, so one contract can be
imported against the mock and later re-pointed at production.

### You cannot edit the model at all — use a forward proxy

See §3. This is the most work and the last resort.

## 2. Prism: serve an OpenAPI contract as a mock

```bash
npm install -g @stoplight/prism-cli      # ~15s
prism mock specs/rates.json --port 4020  # serves the contract's `example` values
```

Everything below cost real time to find out and is not on Prism's front page:

- **Prism mounts paths at the root** and ignores any base path in
  `servers[0].url`. Address it as `http://127.0.0.1:4020`, not
  `http://127.0.0.1:4020/v1` — otherwise every path 404s while the server looks
  perfectly healthy.
- **Make `servers[0].url` absolute in the contract you import.** mxcli's OpenAPI
  import only accepts an `http://` or `https://` URL as `BaseUrl`; a relative one
  (`/api/v3`) is skipped with the warning *"server URL … is relative and cannot
  be used as BaseUrl; set BaseUrl explicitly in CREATE REST CLIENT"*, and a
  client with no `BaseUrl` fails at call time, not at import time.
- **`Prefer: code=404`** on the request forces any status the contract documents.
  This is the only practical way to drive a Mendix error handler through a real
  HTTP response rather than by hand-editing the model.
- **`prism mock -d`** returns schema-generated random data instead of the
  `example` values. Run the suite both ways: a mapping that quietly depends on
  one fixed payload passes under `example` and fails under `-d`.
- **Prism enforces the contract's `security`**, so a call with no `Authorization`
  header gets a real 401. Useful — but know the ceiling before you design around
  it: a REST client document's header value may be a literal, a `$Variable`, or a
  literal **prefix** plus a variable (`'Bearer ' + $Token`), and nothing else. A
  token that must be computed per call belongs in a `REST CALL` expression, not
  in the document.
- **Cut a subset; never point Prism at a vendor's full contract.** The official
  Microsoft Graph spec is 41 MB of YAML: it downloads in seconds and Prism was
  still printing "Starting Prism…" when killed at a 300-second cap. Importing it
  would also generate thousands of operations into the module.

A contract small enough to read is the point. Hand-cut one path with one
`example` per response code you care about, and keep it in the project under
`specs/` next to the `.mpr` — the same relative path the `OpenAPI:` clause takes.

## 3. Forward proxy: when the model cannot change

For an app whose model you must not touch, redirect the JVM instead. The Mendix
runtime honours the standard Java proxy properties, and `mxcli run --local`
passes your environment through to the runtime JVM — including `JAVA_TOOL_OPTIONS`,
which mxcli **appends** to rather than replaces, so an exported value survives
even alongside `--trace`. No model change, nothing committed:

```bash
export JAVA_TOOL_OPTIONS="-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080"
mxcli run --local -p app.mpr
```

Two things to know before committing to this route:

- **HTTPS is the real work.** The proxy must present a certificate the JVM
  trusts. WireMock 3.13.2 on Java 21 cannot generate a usable one; use mitmproxy
  (which ships a CA you install into the JVM truststore) or supply your own
  keystore. Plain `http://` targets need none of this — one more reason to have
  the mock on loopback HTTP.
- **Loopback is not proxied.** `127.0.0.1` sits in the runtime's
  `http.nonProxyHosts`, so app→mock traffic on loopback works *inside* a proxied
  container without any exemption of your own. It also means a proxy on
  `127.0.0.1` will not intercept loopback calls — that is not a bug to hunt.

For **consumed OData** services specifically there is a fourth route that needs
no JVM flags: `System.ConsumedODataConfiguration` carries `ProxyConfiguration`,
`ProxyHost` and `ProxyPort` as data (see `system-module`), so the proxy can be
set per service at runtime.

## Verify the mock before blaming Mendix

Always prove the endpoint from the shell first. A Mendix error message cannot
distinguish "the mock is not running" from "the mapping is wrong".

```bash
curl -sS -i http://127.0.0.1:4020/rates          # 200 + the example payload?
curl -sS -i -H 'Prefer: code=404' http://127.0.0.1:4020/rates
```

Then, and only then, run the microflow and check the payload actually reached it
(`mxcli oql`, or the runtime log under `mxcli run --local`).

## Failure modes, symptoms first

| Symptom | Cause | Fix |
|---|---|---|
| Every path 404s, server looks fine | The client address includes the contract's base path | Address Prism at the root: `http://127.0.0.1:4020` |
| Import produced a client with no `BaseUrl` | `servers[0].url` is relative — mxcli warned and skipped it | Make it absolute, or pass `BaseUrl:` explicitly |
| Prism never finishes starting | Vendor contract is tens of MB | Cut the paths you need into a small contract |
| Mock returns 401 | The contract declares `security`; the call sent no credentials | Add the header, or drop `security` from your cut contract |
| Calls still reach the real API | Proxy properties not applied, or the target is loopback (never proxied) | Check the JVM args; prefer the constant route over a proxy |
| Endpoint swapped but the app disagrees | An override on a different layer wins | `mxcli constant list -p app.mpr` — it names the layer |
| Works with `example` values, fails in CI | The mapping depends on one fixed payload | Run `prism mock -d` locally and fix the mapping |

## Related Skills

- `rest-client` — the three ways to call a REST API; where the contract goes once you have one
- `rest-call-from-json` — JSON structure → entities → import mapping → `REST CALL`
- `test-app` — browser verification; a REST app's prerequisite is a reachable endpoint
- `test-microflows` — running a suite; `--constant` points it at the mock
- `run-local` — `mxcli run --local`, the warm loop the mock plugs into