|
| 1 | +# Mock REST APIs Skill |
| 2 | + |
| 3 | +Use this skill when a REST integration needs an endpoint you control instead of a |
| 4 | +live third-party API — while building it, while reproducing a bug, or while |
| 5 | +verifying the app in a browser or a test run. |
| 6 | + |
| 7 | +Developing against the real API means network, rate limits, credentials, and a |
| 8 | +payload that can change under you. None of that is where Mendix integration |
| 9 | +defects live: those are in the mapping, the entity types, the error handler, and |
| 10 | +the BSON. A mock removes the variables that are not the bug. |
| 11 | + |
| 12 | +## When to Use This Skill |
| 13 | + |
| 14 | +- Building a REST client or `REST CALL` microflow before (or without) real credentials |
| 15 | +- Reproducing a payload-shaped bug **deterministically** — a shape small enough to read, that behaves the same on every run |
| 16 | +- Exercising error paths: 404, 500, a timeout, a 401 from missing auth |
| 17 | +- Verifying the app (`test-app.md`) or running a suite (`test-microflows.md`) offline or in CI |
| 18 | +- Redirecting the outbound calls of an app whose model you must not edit |
| 19 | + |
| 20 | +## Two separate problems |
| 21 | + |
| 22 | +Almost every wasted hour here comes from conflating them: |
| 23 | + |
| 24 | +| Problem | Answer | |
| 25 | +|---|---| |
| 26 | +| **Something must answer the request** | A mock server: Prism (from a contract), WireMock, mitmproxy | |
| 27 | +| **The app must send the request there** | A constant, a `BaseUrl`, or a forward proxy — see below | |
| 28 | + |
| 29 | +A mock server is **not** an interceptor. Prism serves one contract at one port |
| 30 | +and answers only clients that address it. Asking it to "catch all calls the app |
| 31 | +already makes" is a category error — that is the forward-proxy job, further down. |
| 32 | + |
| 33 | +## 1. Point the app at the mock |
| 34 | + |
| 35 | +Three routes, cheapest first. Pick by how the URL is built. |
| 36 | + |
| 37 | +### The URL is built in the microflow — use a constant, change nothing per run |
| 38 | + |
| 39 | +A `REST CALL` URL is an **expression**, so it can be assembled from a constant |
| 40 | +(`@Module.Constant` is Mendix's constant reference — `$Name` is a *variable*): |
| 41 | + |
| 42 | +```sql |
| 43 | +create constant MyModule.ApiBaseUrl type String default 'https://api.example.com/v1'; |
| 44 | + |
| 45 | +create microflow MyModule.CallApi() returns string |
| 46 | +begin |
| 47 | + $response = rest call get @MyModule.ApiBaseUrl + '/rates' |
| 48 | + header Accept = 'application/json' |
| 49 | + returns string; |
| 50 | + return $response; |
| 51 | +end; |
| 52 | +``` |
| 53 | + |
| 54 | +Then swap the endpoint per run, with no model change and nothing committed: |
| 55 | + |
| 56 | +```bash |
| 57 | +# this run only — never written to the project |
| 58 | +mxcli run --local -p app.mpr --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020 |
| 59 | + |
| 60 | +# a test suite against the mock (--constant needs --local) |
| 61 | +mxcli test tests/ -p app.mpr --local --constant MyModule.ApiBaseUrl=http://127.0.0.1:4020 |
| 62 | + |
| 63 | +# machine-local default, gitignored: every run picks it up |
| 64 | +mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr |
| 65 | + |
| 66 | +# flip it on an app that is already running |
| 67 | +mxcli constant set MyModule.ApiBaseUrl http://127.0.0.1:4020 -p app.mpr --apply |
| 68 | +``` |
| 69 | + |
| 70 | +`constant set` refuses a name the project does not define, so a typo cannot |
| 71 | +silently apply to nothing. `mxcli constant list -p app.mpr` shows the winning |
| 72 | +value for every constant **and which layer set it** — read it first whenever a |
| 73 | +run does not use the endpoint you expected. |
| 74 | + |
| 75 | +### The call goes through a REST client document — rewrite `BaseUrl` |
| 76 | + |
| 77 | +A REST client document's `BaseUrl` is a **literal**; it cannot reference a |
| 78 | +constant. Point it at the mock by re-running the create, which is a one-line diff: |
| 79 | + |
| 80 | +```sql |
| 81 | +create or modify rest client MyModule.RatesAPI ( |
| 82 | + OpenAPI: 'specs/rates.json', |
| 83 | + BaseUrl: 'http://127.0.0.1:4020' |
| 84 | +); |
| 85 | +``` |
| 86 | + |
| 87 | +`BaseUrl` also overrides `servers[0].url` at import time, so one contract can be |
| 88 | +imported against the mock and later re-pointed at production. |
| 89 | + |
| 90 | +### You cannot edit the model at all — use a forward proxy |
| 91 | + |
| 92 | +See §3. This is the most work and the last resort. |
| 93 | + |
| 94 | +## 2. Prism: serve an OpenAPI contract as a mock |
| 95 | + |
| 96 | +```bash |
| 97 | +npm install -g @stoplight/prism-cli # ~15s |
| 98 | +prism mock specs/rates.json --port 4020 # serves the contract's `example` values |
| 99 | +``` |
| 100 | + |
| 101 | +Everything below cost real time to find out and is not on Prism's front page: |
| 102 | + |
| 103 | +- **Prism mounts paths at the root** and ignores any base path in |
| 104 | + `servers[0].url`. Address it as `http://127.0.0.1:4020`, not |
| 105 | + `http://127.0.0.1:4020/v1` — otherwise every path 404s while the server looks |
| 106 | + perfectly healthy. |
| 107 | +- **Make `servers[0].url` absolute in the contract you import.** mxcli's OpenAPI |
| 108 | + import only accepts an `http://` or `https://` URL as `BaseUrl`; a relative one |
| 109 | + (`/api/v3`) is skipped with the warning *"server URL … is relative and cannot |
| 110 | + be used as BaseUrl; set BaseUrl explicitly in CREATE REST CLIENT"*, and a |
| 111 | + client with no `BaseUrl` fails at call time, not at import time. |
| 112 | +- **`Prefer: code=404`** on the request forces any status the contract documents. |
| 113 | + This is the only practical way to drive a Mendix error handler through a real |
| 114 | + HTTP response rather than by hand-editing the model. |
| 115 | +- **`prism mock -d`** returns schema-generated random data instead of the |
| 116 | + `example` values. Run the suite both ways: a mapping that quietly depends on |
| 117 | + one fixed payload passes under `example` and fails under `-d`. |
| 118 | +- **Prism enforces the contract's `security`**, so a call with no `Authorization` |
| 119 | + header gets a real 401. Useful — but know the ceiling before you design around |
| 120 | + it: a REST client document's header value may be a literal, a `$Variable`, or a |
| 121 | + literal **prefix** plus a variable (`'Bearer ' + $Token`), and nothing else. A |
| 122 | + token that must be computed per call belongs in a `REST CALL` expression, not |
| 123 | + in the document. |
| 124 | +- **Cut a subset; never point Prism at a vendor's full contract.** The official |
| 125 | + Microsoft Graph spec is 41 MB of YAML: it downloads in seconds and Prism was |
| 126 | + still printing "Starting Prism…" when killed at a 300-second cap. Importing it |
| 127 | + would also generate thousands of operations into the module. |
| 128 | + |
| 129 | +A contract small enough to read is the point. Hand-cut one path with one |
| 130 | +`example` per response code you care about, and keep it in the project under |
| 131 | +`specs/` next to the `.mpr` — the same relative path the `OpenAPI:` clause takes. |
| 132 | + |
| 133 | +## 3. Forward proxy: when the model cannot change |
| 134 | + |
| 135 | +For an app whose model you must not touch, redirect the JVM instead. The Mendix |
| 136 | +runtime honours the standard Java proxy properties, and `mxcli run --local` |
| 137 | +passes your environment through to the runtime JVM — including `JAVA_TOOL_OPTIONS`, |
| 138 | +which mxcli **appends** to rather than replaces, so an exported value survives |
| 139 | +even alongside `--trace`. No model change, nothing committed: |
| 140 | + |
| 141 | +```bash |
| 142 | +export JAVA_TOOL_OPTIONS="-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=8080" |
| 143 | +mxcli run --local -p app.mpr |
| 144 | +``` |
| 145 | + |
| 146 | +Two things to know before committing to this route: |
| 147 | + |
| 148 | +- **HTTPS is the real work.** The proxy must present a certificate the JVM |
| 149 | + trusts. WireMock 3.13.2 on Java 21 cannot generate a usable one; use mitmproxy |
| 150 | + (which ships a CA you install into the JVM truststore) or supply your own |
| 151 | + keystore. Plain `http://` targets need none of this — one more reason to have |
| 152 | + the mock on loopback HTTP. |
| 153 | +- **Loopback is not proxied.** `127.0.0.1` sits in the runtime's |
| 154 | + `http.nonProxyHosts`, so app→mock traffic on loopback works *inside* a proxied |
| 155 | + container without any exemption of your own. It also means a proxy on |
| 156 | + `127.0.0.1` will not intercept loopback calls — that is not a bug to hunt. |
| 157 | + |
| 158 | +For **consumed OData** services specifically there is a fourth route that needs |
| 159 | +no JVM flags: `System.ConsumedODataConfiguration` carries `ProxyConfiguration`, |
| 160 | +`ProxyHost` and `ProxyPort` as data (see `system-module.md`), so the proxy can be |
| 161 | +set per service at runtime. |
| 162 | + |
| 163 | +## Verify the mock before blaming Mendix |
| 164 | + |
| 165 | +Always prove the endpoint from the shell first. A Mendix error message cannot |
| 166 | +distinguish "the mock is not running" from "the mapping is wrong". |
| 167 | + |
| 168 | +```bash |
| 169 | +curl -sS -i http://127.0.0.1:4020/rates # 200 + the example payload? |
| 170 | +curl -sS -i -H 'Prefer: code=404' http://127.0.0.1:4020/rates |
| 171 | +``` |
| 172 | + |
| 173 | +Then, and only then, run the microflow and check the payload actually reached it |
| 174 | +(`mxcli oql`, or the runtime log under `mxcli run --local`). |
| 175 | + |
| 176 | +## Failure modes, symptoms first |
| 177 | + |
| 178 | +| Symptom | Cause | Fix | |
| 179 | +|---|---|---| |
| 180 | +| 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` | |
| 181 | +| Import produced a client with no `BaseUrl` | `servers[0].url` is relative — mxcli warned and skipped it | Make it absolute, or pass `BaseUrl:` explicitly | |
| 182 | +| Prism never finishes starting | Vendor contract is tens of MB | Cut the paths you need into a small contract | |
| 183 | +| Mock returns 401 | The contract declares `security`; the call sent no credentials | Add the header, or drop `security` from your cut contract | |
| 184 | +| 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 | |
| 185 | +| Endpoint swapped but the app disagrees | An override on a different layer wins | `mxcli constant list -p app.mpr` — it names the layer | |
| 186 | +| Works with `example` values, fails in CI | The mapping depends on one fixed payload | Run `prism mock -d` locally and fix the mapping | |
| 187 | + |
| 188 | +## Related Skills |
| 189 | + |
| 190 | +- `rest-client.md` — the three ways to call a REST API; where the contract goes once you have one |
| 191 | +- `rest-call-from-json.md` — JSON structure → entities → import mapping → `REST CALL` |
| 192 | +- `test-app.md` — browser verification; a REST app's prerequisite is a reachable endpoint |
| 193 | +- `test-microflows.md` — running a suite; `--constant` points it at the mock |
| 194 | +- `run-local.md` — `mxcli run --local`, the warm loop the mock plugs into |
0 commit comments