Pretender
A mock server library with a nice routing DSL
Install / Use
/learn @pretenderjs/PretenderREADME
Pretender
Pretender is a mock server library for XMLHttpRequest and Fetch, that comes with an express/sinatra style syntax for defining routes and their handlers.
Pretender will temporarily replace native XMLHttpRequest and Fetch , intercept all requests, and direct them to little pretend service you've defined.
:warning: Pretender only works in the browser!
const PHOTOS = {
"10": {
id: 10,
src: 'http://media.giphy.com/media/UdqUo8xvEcvgA/giphy.gif'
},
"42": {
id: 42,
src: 'http://media0.giphy.com/media/Ko2pyD26RdYRi/giphy.gif'
}
};
const server = new Pretender(function() {
this.get('/photos', request => {
let all = JSON.stringify(Object.keys(PHOTOS).map(k => PHOTOS[k]));
return [200, {"Content-Type": "application/json"}, all]
});
this.get('/photos/:id', request => {
return [200, {"Content-Type": "application/json"}, JSON.stringify(PHOTOS[request.params.id])]
});
});
$.get('/photos/12', {success() => { ... }})
Usage
yarn add -D pretender
# or
npm install --save-dev pretender
You can load Pretender directly in the browser.
<script src="node_modules/pretender/dist/pretender.bundle.js"></script>
Or as a module:
import Pretender from 'pretender';
const server = new Pretender(function() {});
The Server DSL
The server DSL is inspired by express/sinatra. Pass a function to the Pretender constructor
that will be invoked with the Pretender instance as its context. Available methods are
get, put, post, delete, patch, and head. Each of these methods takes a path pattern,
a callback, and an optional timing parameter. The callback will be invoked with a
single argument (the XMLHttpRequest instance that triggered this request) and must return an array
containing the HTTP status code, headers object, and body as a string.
const server = new Pretender(function() {
this.put('/api/songs/99', request => [404, {}, ""]);
});
a Pretender constructor can take multiple maps:
import adminMaps from "testing/maps/admin";
import photoMaps from "testing/maps/photos";
const server = new Pretender(photoMaps, adminMaps);
// testing/maps/photos
const PHOTOS = {
"58": {
id: 58,
src: 'https://media.giphy.com/media/65TpAhHZ7A9nuf3GIu/giphy.gif'
},
"99": {
id: 99,
src: 'https://media.giphy.com/media/4Zd5qAcFv759xnegdo/giphy.gif'
}
};
export default function() {
this.get('/photos/:id', () =>
[200, {"Content-Type": "application/json"}, JSON.stringify(PHOTOS[request.params.id])]
);
}
The HTTP verb methods can also be called on an instance individually:
const server = new Pretender();
server.put('/api/songs/99', request => [404, {}, ""]);
Paths
Paths can either be hard-coded (this.get('/api/songs/12')) or contain dynamic segments
(this.get('/api/songs/:song_id'). If there were dynamic segments of the path,
these will be attached to the request object as a params property with keys matching
the dynamic portion and values with the matching value from the path.
const server = new Pretender(function() {
this.get('/api/songs/:song_id', request => request.params.song_id);
});
$.get('/api/songs/871') // params.song_id will be '871'
Query Parameters
If there were query parameters in the request, these will be attached to the request object as a queryParams
property.
const server = new Pretender(function() {
this.get('/api/songs', request => request.queryParams.sortOrder);
});
// typical jQuery-style uses you've probably seen.
// queryParams.sortOrder will be 'asc' for both styles.
$.get({url: '/api/songs', data: { sortOrder: 'asc' }});
$.get('/api/songs?sortOrder=asc');
Responding
You must return an array from this handler that includes the HTTP status code, an object literal of response headers, and a string body.
const server = new Pretender(function() {
this.get('/api/songs', request => {
return [
200,
{'content-type': 'application/javascript'},
'[{"id": 12}, {"id": 14}]'
];
});
});
Or, optionally, return a Promise.
const server = new Pretender(function() {
this.get('/api/songs', request => {
return new Promise(resolve => {
let response = [
200,
{'content-type': 'application/javascript'},
'[{"id": 12}, {"id": 14}]'
];
resolve(response);
});
});
});
Pass-Through
You can specify paths that should be ignored by pretender and made as real XHR requests.
Enable these by specifying pass-through routes with pretender.passthrough:
const server = new Pretender(function() {
this.get('/photos/:id', this.passthrough);
});
In some cases, you will need to force pretender to passthough, just start your server with the forcePassthrough option.
const server = new Pretender({ forcePassthrough: true })
Other times, you may want to decide whether or not to passthrough when the call is made. In that
case you can use the .passthrough() function on the fake request itself. (The unhandledRequest
property is discussed below.)
server.unhandledRequest = function(verb, path, request) {
if (myIgnoreRequestChecker(path)) {
console.warn(`Ignoring request) ${verb.toUpperCase()} : ${path}`);
} else {
console.warn(
`Unhandled ${verb.toUpperCase()} : ${path} >> Passing along. See eventual response below.`
)
const xhr = request.passthrough(); // <-- A native, sent xhr is returned
xhr.onloadend = (ev) => {
console.warn(`Response for ${path}`, {
verb,
path,
request,
responseEvent: ev,
})
};
}
};
The .passthrough() function will immediately create, send, and return a native XMLHttpRequest.
Timing Parameter
The timing parameter is used to control when a request responds. By default, a request responds asynchronously on the next frame of the browser's event loop. A request can also be configured to respond synchronously, after a defined amount of time, or never (i.e., it needs to be manually resolved).
Default
const server = new Pretender(function() {
// songHandler will execute the frame after receiving a request (async)
this.get('/api/songs', songHandler);
});
Synchronous
const server = new Pretender(function() {
// songHandler will execute immediately after receiving a request (sync)
this.get('/api/songs', songHandler, false);
});
Delay
const server = new Pretender(function() {
// songHandler will execute two seconds after receiving a request (async)
this.get('/api/songs', songHandler, 2000);
});
Manual
const server = new Pretender(function() {
// songHandler will only execute once you manually resolve the request
this.get('/api/songs', songHandler, true);
});
// resolve a request like this
server.resolve(theXMLHttpRequestThatRequestedTheSongsRoute);
Using functions for the timing parameter
You may want the timing behavior of a response to change from request to request. This can be done by providing a function as the timing parameter.
const externalState = 'idle';
function throttler() {
if (externalState === 'OH NO DDOS ATTACK') {
return 15000;
}
}
const server = new Pretender(function() {
// songHandler will only execute based on the result of throttler
this.get('/api/songs', songHandler, throttler);
});
Now whenever the songs route is requested, its timing behavior will be determined by the result
of the call to throttler. When externalState is idle, throttler returns undefined, which
means the route will use the default behavior.
When the time is right, you can set externalState to "OH NO DOS ATTACK" which will make all
future requests take 15 seconds to respond.
Scheduling ProgressEvent
If the timing parameter is resolved as async, then a ProgressEvent
will be scheduled every 50ms until the request has a response or is aborted.
To listen to the progress, you can define onprogress on the XMLHttpRequest object or
its upload attribute.
let xhr = new window.XMLHttpRequest();
xhr.open('POST', '/uploads');
// https://fetch.spec.whatwg.org/#concept-request-body
// https://xhr.spec.whatwg.org/#the-send()-method
let postBody = new ArrayBuffer(8);
xhr.upload.onprogress = function(event) {
// event.lengthComputable === true
// event.total === 8
// event.loaded will be incremented every ~50ms
};
xhr.onprogress = function(event) {
// xhr onprogress will also be triggered
};
xhr.send(postBody);
Sharing routes
You can call map multiple times on a Pretender instance. This is a great way to share and reuse
sets of routes between tests:
export function authenticationRoutes() {
this.post('/authenticate',() => { ... });
this.post('/sig
Related Skills
node-connect
333.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
82.0kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
333.7kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
82.0kCommit, push, and open a PR
