A mock OData V2 and V4 server generated from your service's metadata.xml. Point it at the file, and it serves the service as V2 and V4 at once, with mock data or your own CSV files. No project, no code and no SAP system needed.
prova (πρόβα): Greek for rehearsal
Not affiliated with or endorsed by SAP.
Anyone whose work depends on an SAP OData service they can't always reach: Fiori and UI5 developers, UX designers, web, mobile and integration developers, test and DevOps engineers, and consultants giving demos.
Where it helps:
- Before the backend exists: the service's
metadata.xmlis enough. - Without the SAP system: no VPN, missing authorizations or a system that's down. The app keeps its paths through the
fiori-tools-proxy(see Your app). - In CI: a Docker image with the same data on every run, for OPA5 and other browser tests.
- Moving from V2 to V4: one
metadata.xmlserves both, side by side on the same data. - In demos: data you choose, for people without access to the system.
It's not a backend: no business logic beyond required values and config.json rules, and writes last until a restart.
A folder with your service's metadata.xml is all it needs:
npx odata-prova ./MySrvodata-prova listening on port 3000
V2: http://localhost:3000/odata/v2/MySrv/
V4: http://localhost:3000/odata/v4/MySrv/
If port 3000 is already in use, pick another with
--port, e.g.npx odata-prova ./MySrv --port 4004
Or with Docker, mounting the folder as /models/<ServiceName>:
docker run -p 3000:3000 -v "$PWD/MySrv:/models/MySrv:ro" ghcr.io/jaravan/odata-provaThe image is built for amd64 and arm64, and tagged by version (e.g. 0.15.0 or 0.15) and latest.
examples/ has three services to try: PurchaseOrderSrv (V2, with a function import), Northwind (V4) and TripPin (V4, with actions and functions).
Tip
See it in a full project: fiori-devstack runs V2 and V4 Fiori Elements apps on odata-prova, with Docker Compose or Kubernetes, and OPA tests in CI.
MySrv/
metadata.xml # the service's $metadata, V2 or V4
data/ # optional, one file per entity set (the others get mock data)
MyEntitySet.csv # header row, ; or , separated, quoted as spreadsheets write it (JSON also works)
config.json # optional, what operations change (see docs/operations.md)
The folder name becomes the service name: /odata/v2/MySrv/ and /odata/v4/MySrv/. Whichever protocol the metadata.xml wasn't written for gets its metadata generated from the other, with its annotations: a V2 document's sap: attributes become V4 annotations, and a V4 document's annotations are written for UI5's V2 model. A data file is named after the entity set, its entity type, or <namespace>-<EntityType> (first match wins). Writes are kept in memory until a restart resets them.
Each entity set without a data file gets 20 generated rows that fit the metadata, with foreign keys pointing at real rows so navigation and $expand work. odata-prova-mock-data writes them to files for you to edit, see Mock data.
Point your app's backend at the server. In a Fiori tools app, that's the fiori-tools-proxy backend in ui5.yaml, where pathReplace maps the path your app requests to the server's, so the app itself doesn't change:
backend:
- path: /sap/opu/odata/sap/MySrv
pathReplace: /odata/v2/MySrv
url: http://localhost:3000Use /odata/v4/MySrv for a V4 app. The server also sends CORS headers, so apps can call it directly.
The server serves only the service and its $metadata. Annotations your app loads from the system's catalog service (an IWFND/CATALOGSERVICE data source in manifest.json) aren't available; annotation files in the app work as usual.
odata-prova [modelDir] [options] serves the model in modelDir, or in the current directory. Every setting is optional, and is an option or an environment variable: the option works in any shell, the variable suits Docker and Kubernetes, and the option wins. odata-prova --help lists them.
npx odata-prova ./MySrv --port 4004 --mock-rows 50 --etags strict| Option | Variable | Default | Purpose |
|---|---|---|---|
-p, --port |
PORT |
3000 |
HTTP port |
modelDir |
MODEL_DIR |
the current directory (/models in the image) |
The model to serve, or a folder holding exactly one model |
--service-name |
SERVICE_NAME |
the model's folder name | Used to build the default service paths |
--v2-path |
V2_PATH |
/odata/v2/<SERVICE_NAME> |
V2 service root - set to "" to disable V2 |
--v4-path |
V4_PATH |
/odata/v4/<SERVICE_NAME> |
V4 service root - set to "" to disable V4 |
--mock-rows |
MOCK_ROWS |
20 |
Rows generated for each entity set without a data file - 0 leaves it empty |
--etags |
ETAGS |
lenient |
How strictly If-Match is checked: lenient, strict or off, see ETags |
--joins |
JOINS |
guess |
A navigation without a ReferentialConstraint: guess its join from property names, or strict to give it a key of its own instead, see Navigations without a ReferentialConstraint |
--page-size |
PAGE_SIZE |
0 |
The most rows a collection's response holds, with a next link to the rest - 0 sends every row, see Server-driven paging |
The image contains no model. Mount one as /models/<ServiceName>, and the server finds it. To mount several, set MODEL_DIR to the one to serve, e.g. -v "$PWD/examples:/models:ro" -e MODEL_DIR=/models/Northwind.
Important
Run a single instance per service. Each instance keeps its own in-memory copy of the data, so with more than one replica, writes land on one and reads may come from another.
- Reads and writes, including deep insert, links to existing entities (
@odata.bind,$ref, V2's$links), and$batchwith atomic changesets. - Query options:
$filter(with the V4 lambda operatorsanyandall),$orderby,$top,$skip,$select,$expand(with nested V4 options),$count/$inlinecount,$search, V4's$compute, server-driven paging with$skiptoken(PAGE_SIZE), and V4's$applyfor UI5's grouped and aggregated tables, totals included (Aggregation). - Types: all primitive Edm types, complex and enum types, collections and inheritance.
- Actions and functions, bound and imported, served on both protocols: a V2 function import with
sap:action-forbecomes a V4 bound action. Aconfig.jsonrule can make one change data, e.g. setStatustoApproved. - Drafts (
Common.DraftRoot, from CAP or RAP, V4 or V2): the Fiori Elements create and edit flows - create, edit, change, save or discard an entity and its compositions in a draft. See Drafts. - ETags (
Core.OptimisticConcurrency, or V2'sConcurrencyMode="Fixed"): a change sent with an ETag that's gone stale is a412.ETAGS=strictalso requires one, andETAGS=offignores it. See ETags. - Delta links: read an entity set with
Prefer: odata.track-changes, and its delta link later answers what was added, changed or deleted since:@odata.deltaLinkin V4, SAP Gateway's__deltawith!deltatokenin V2. See Delta links.
What $apply doesn't cover is rejected with 501, see Aggregation.
The tests load Northwind (V2 and V4) and TripPin unmodified, with every navigation working, including those with no foreign key in the metadata (Navigations without a ReferentialConstraint).
- fe-mockserver runs as middleware inside
ui5 serve, next to the app that hosts it. Its core can be mounted in your own Express server, but SAP documents it as not meant for direct use. odata-prova is a standalone server or container that any app, pipeline or test can share. - CAP can mock a service too:
cds importconverts itsmetadata.xmlto CDS, andcds watchserves it with CSV data, but that needs a CAP project around it. odata-prova uses the file as is, with no project or code, and serves it as V2 and V4 at once.
For quick UI work inside a single app, the fe-mockserver is still simpler.
- Mock data: how rows are generated, and editing them as files
- Supported OData features: types, query options, and what's rejected
- Actions and functions: how operations answer, the V2/V4 mapping, and
config.jsonrules - Architecture: how it's built, using it in code, planned improvements, and the specs it follows
Issues and pull requests are welcome, see CONTRIBUTING.md.
