mockey is a lightweight, code-first mock API server built on Express.
It routes incoming requests to resolver functions, maps resolver keys to response files, and returns JSON/text/file/redirect responses with optional delay and dynamic data mutation.
- Mock multiple endpoints with
GET/POST - Chain multiple resolvers for a single route (first match wins)
- Return static JSON/text, dynamic responses, files, or redirects
- Global delay + per-response delay override
- Simple auth/JWKS simulation endpoints
- Ready demo APIs for order + loan workflows
- Node.js 18+
- npm
npm install
npm startServer starts on:
http://localhost:31333(orPORTenv var if provided)
Health check style endpoint:
curl "http://localhost:31333/ping"src/
main.js # app bootstrap
config/mockey-config.json # global config (delay)
route/mockey-route.json # routeKey -> response file mapping
resolver/
core/resolver.registry.js # register endpoint + resolver(s)
provider/*.resolver.js # resolver implementations
response/
... # mocked payload files
lib/
api/api.controller.js # binds Express handlers
helper/data.controller.js # response extraction + delay + send
In src/resolver/core/resolver.registry.js, each resolver is registered by:
- HTTP method
- path
- resolver instance
Example:
resolver.register('POST', '/generate', new TestResolver());You can register multiple resolvers for the same method+path. They run in order.
For an incoming request:
- framework runs each resolver’s
resolve(req, res) - first truthy return value is selected as
routeKey - if nothing matches, fallback key is
404
src/route/mockey-route.json maps routeKey to a response file under src/response.
Example:
{
"404": "404.json",
"TEST": "/test/test.json"
}If resolver has process(data, req, res), it can mutate response before sending.
Resolver options supported:
responseType: 'string'-> read response file as plain textresponseHeaders: { ... }-> custom response headerssendFile: true-> send file path directlyredirect: true-> send 301 redirect to returned URL
- Global delay from
src/config/mockey-config.json(responseDelayInMillis) - Per-response override by adding
responseDelayInMillisin response JSON file
GET /pingPOST /generate(multiple resolvers registered)
GET /login(serves login HTML page)GET /redirectToSource(builds redirect URL withstate+code)POST /authenticate(returns id_token)GET /jwks(returns mock JWKS)
POST /api/mock/order/submitGET /api/mock/order/status?orderId=ORD-1001GET /api/mock/order/async/trace?orderId=ORD-1001GET /api/mock/customer/profile?customerId=CUST-1001
GET /api/mock/loan/credit-union/rating?customerId=CUST-1001GET /api/mock/loan/credit-card/fraud-check?customerId=CUST-1001GET /api/mock/loan/debt-credit/summary?customerId=CUST-1001POST /api/mock/loan/application/submit
curl -X POST "http://localhost:31333/api/mock/order/submit" \
-H "Content-Type: application/json" \
-d '{"customerId":"CUST-1001","orderId":"ORD-7007","submittedByRole":"ADMIN"}'curl "http://localhost:31333/api/mock/loan/credit-union/rating?customerId=CUST-LOW"curl -X POST "http://localhost:31333/api/mock/loan/application/submit" \
-H "Content-Type: application/json" \
-d '{"customerId":"CUST-1001","requestedAmount":350000,"tenureMonths":36}'Create src/resolver/provider/customer-data.resolver.js:
const { JsonPathBuilder, JsonPathUtil } = require("../../lib/helper/jsonpath.util");
exports.CustomerDataResolver = function () {
return {
resolve: function (request) {
const body = request.body || {};
const path = new JsonPathBuilder()
.arraynode("config")
.select(0)
.arraynode("components")
.select(0)
.build();
const value = new JsonPathUtil().search(body, path);
return value && value[0] === "CUSTOMER_DATA_SCENARIO_1"
? "CUSTOMER_DATA_SCENARIO_1"
: null;
},
process: function (data) {
data.transactionId = "TRX-" + Date.now();
}
};
};Edit src/resolver/core/resolver.registry.js:
const { CustomerDataResolver } = require("../provider/customer-data.resolver");
function registerCustomerDataResolver(resolver) {
resolver.register("POST", "/customerData", new CustomerDataResolver());
}
// inside init()
registerCustomerDataResolver(resolver);Edit src/route/mockey-route.json:
{
"CUSTOMER_DATA_SCENARIO_1": "/customer/customerData.json"
}Create src/response/customer/customerData.json:
{
"responseDelayInMillis": 300,
"status": "SUCCESS",
"data": {
"items": []
}
}curl -X POST "http://localhost:31333/customerData" \
-H "Content-Type: application/json" \
-d '{
"config":[{"components":["CUSTOMER_DATA_SCENARIO_1"]}],
"inputParams":{"paginationRequest":{"currentPage":1,"pageSize":100}}
}'A resolver can implement any of these properties:
{
resolve: (req, res) => "ROUTE_KEY" | null,
process: (data, req, res) => void,
responseType: "string", // optional
responseHeaders: { "Header": "Value" }, // optional
sendFile: true, // optional
redirect: true // optional
}Behavior:
resolvedecides which route key to use.processmutates loaded data.responseType: 'string'for txt/plain payload files.sendFilebypasses JSON parsing and sends file.redirectsends HTTP301redirect.
src/config/mockey-config.json
{
"name": "App Name",
"responseDelayInMillis": 1000
}Any JSON payload can override delay:
{
"responseDelayInMillis": 2500,
"data": {}
}Framework removes responseDelayInMillis from response body before sending.
- Always getting 404 response:
- Ensure resolver is registered for correct method+path.
- Ensure
resolve()returns a key that exists inmockey-route.json.
- Resolver is called but wrong payload returns:
- Check resolver order for same method+path (first truthy match wins).
- Text response coming as JSON parse error:
- Set
responseType: 'string'in resolver.
- Delay not applying:
- Check global config and payload-level
responseDelayInMillistype (number/string parseable).
- File/redirect behavior not working:
- Use
sendFile: trueorredirect: trueexplicitly in resolver.
- Default internal port constant is
31333. process.env.PORTtakes precedence at runtime.- API responses are loaded from
src/responsebased on route mapping.
These mocks are ready to support ConvEngine MCP and HTTP tool testing.
Use these APIs in sequence when testing order-status troubleshooting scenarios:
- Submit order
curl -X POST "http://localhost:31333/api/mock/order/submit" \
-H "Content-Type: application/json" \
-d '{
"orderId":"ORD-7007",
"customerId":"CUST-1001",
"submittedByRole":"ADMIN",
"sourceCity":"Mumbai",
"targetCity":"Bengaluru"
}'- Check order status
curl "http://localhost:31333/api/mock/order/status?orderId=ORD-7007"- Trace async callback
curl "http://localhost:31333/api/mock/order/async/trace?orderId=ORD-7007"- Fetch customer profile
curl "http://localhost:31333/api/mock/customer/profile?customerId=CUST-1001"Notes for ConvEngine behavior testing:
orderIdending with9returns statusFAILED.orderIdending with7returns async fields asnullin trace/status flow.
Use these APIs in sequence for MCP planner/tool-chain testing:
- Credit rating
curl "http://localhost:31333/api/mock/loan/credit-union/rating?customerId=CUST-1001"- Fraud check
curl "http://localhost:31333/api/mock/loan/credit-card/fraud-check?customerId=CUST-1001"- Debt summary
curl "http://localhost:31333/api/mock/loan/debt-credit/summary?customerId=CUST-1001"- Submit loan application
curl -X POST "http://localhost:31333/api/mock/loan/application/submit" \
-H "Content-Type: application/json" \
-d '{
"customerId":"CUST-1001",
"requestedAmount":350000,
"tenureMonths":36
}'Useful test customer IDs:
CUST-LOW-> lower rating pathCUST-EDGE-> boundary rating path (750)CUST-FRAUD-> fraud flagged pathCUST-HIGH-DEBT-> poor debt profile path
curl "http://localhost:31333/ping"Expected:
- HTTP 200
- plain text response from mapped ping route
Run after npm start:
curl -i "http://localhost:31333/ping"
curl -i "http://localhost:31333/api/mock/order/status?orderId=ORD-1001"
curl -i "http://localhost:31333/api/mock/loan/credit-union/rating?customerId=CUST-1001"If these pass, server, resolver registration, route mapping, and response file loading are all healthy.
Use these minimal templates to create new mocks quickly.
Resolver (src/resolver/provider/sample-json.resolver.js):
exports.SampleJsonResolver = function () {
return {
resolve: function () {
return "SAMPLE_JSON_OK";
},
process: function (data, req) {
data.requestId = req.headers["x-request-id"] || "REQ-" + Date.now();
}
};
};Route map (src/route/mockey-route.json):
{
"SAMPLE_JSON_OK": "/sample/json-ok.json"
}Payload (src/response/sample/json-ok.json):
{
"status": "SUCCESS",
"message": "JSON mock response"
}Resolver (src/resolver/provider/sample-text.resolver.js):
exports.SampleTextResolver = function () {
return {
resolve: function () {
return "SAMPLE_TEXT_OK";
},
responseType: "string"
};
};Route map:
{
"SAMPLE_TEXT_OK": "/sample/text-ok.txt"
}Payload (src/response/sample/text-ok.txt):
pong-from-text-mock
Resolver (src/resolver/provider/sample-redirect.resolver.js):
exports.SampleRedirectResolver = function () {
return {
resolve: function (req) {
const state = req.query.state || "NA";
return "https://example.com/callback?state=" + state;
},
redirect: true
};
};No mockey-route.json entry is needed for this pattern because resolver returns final redirect URL directly.
Resolver (src/resolver/provider/sample-file.resolver.js):
const path = require("path");
exports.SampleFileResolver = function () {
return {
resolve: function () {
return path.join("response", "sample", "sample.html");
},
sendFile: true,
responseHeaders: {
"Content-Type": "text/html; charset=utf-8"
}
};
};Payload file:
src/response/sample/sample.html
Add to src/resolver/core/resolver.registry.js:
const { SampleJsonResolver } = require("../provider/sample-json.resolver");
const { SampleTextResolver } = require("../provider/sample-text.resolver");
const { SampleRedirectResolver } = require("../provider/sample-redirect.resolver");
const { SampleFileResolver } = require("../provider/sample-file.resolver");
function registerSampleResolvers(resolver) {
resolver.register("GET", "/sample/json", new SampleJsonResolver());
resolver.register("GET", "/sample/text", new SampleTextResolver());
resolver.register("GET", "/sample/redirect", new SampleRedirectResolver());
resolver.register("GET", "/sample/file", new SampleFileResolver());
}
// inside init()
registerSampleResolvers(resolver);curl "http://localhost:31333/sample/json"
curl "http://localhost:31333/sample/text"
curl -i "http://localhost:31333/sample/redirect?state=abc123"
curl -i "http://localhost:31333/sample/file"