Security News
Research
Supply Chain Attack on Rspack npm Packages Injects Cryptojacking Malware
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
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 is a library for creating a mock server in the browser. It allows developers to intercept and handle HTTP requests, making it useful for testing and development without needing a real backend.
Intercepting HTTP Requests
This feature allows you to intercept HTTP GET requests to a specified URL and return a custom response. In this example, a GET request to '/api/data' will return a 200 status code with a JSON response containing sample data.
const server = new Pretender(function() {
this.get('/api/data', () => {
return [200, { 'Content-Type': 'application/json' }, JSON.stringify({ data: 'sample data' })];
});
});
Handling Different HTTP Methods
Pretender can handle various HTTP methods such as POST, PUT, DELETE, etc. This example demonstrates handling a POST request to '/api/data', parsing the request body, and returning a custom response.
const server = new Pretender(function() {
this.post('/api/data', (request) => {
let requestBody = JSON.parse(request.requestBody);
return [201, { 'Content-Type': 'application/json' }, JSON.stringify({ message: 'Data received', data: requestBody })];
});
});
Simulating Delays
You can simulate network delays to test how your application handles slow responses. In this example, a 1000ms delay is added to the GET request to '/api/data'.
const server = new Pretender(function() {
this.get('/api/data', (request) => {
return [200, { 'Content-Type': 'application/json' }, JSON.stringify({ data: 'sample data' })];
}, 1000); // 1000ms delay
});
Nock is a HTTP mocking and expectations library for Node.js. It intercepts HTTP requests and allows you to define custom responses. Unlike Pretender, which is designed for use in the browser, Nock is primarily used in a Node.js environment.
Mock Service Worker (MSW) is a library for intercepting and mocking network requests in both browser and Node.js environments. It provides a more modern API and supports service worker-based interception, making it more versatile compared to Pretender.
Fetch-mock is a library for mocking fetch requests. It provides a simple API for intercepting fetch calls and returning custom responses. Fetch-mock is focused on fetch API, whereas Pretender can handle a wider range of HTTP methods and scenarios.
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() => { ... }})
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 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 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'
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');
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);
});
});
});
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
.
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);
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.
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);
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('/signout', () => { ... });
}
export function songsRoutes() {
this.get('/api/songs',() => { ... });
}
// a test
import {authenticationRoutes, songsRoutes} from "../shared/routes";
import Pretender from "pretender";
let p = new Pretender();
p.map(authenticationRoutes);
p.map(songsRoutes);
In addition to responding to the request, your server will call a handledRequest
method with
the HTTP verb
, path
, and original request
. By default this method does nothing. You can
override this method to supply your own behavior like logging or test framework integration:
const server = new Pretender(function() {
this.put('/api/songs/:song_id', request => {
return [202, {"Content-Type": "application/json"}, "{}"]
});
});
server.handledRequest = function(verb, path, request) {
console.log("a request was responded to");
}
$.getJSON("/api/songs/12");
Your server will call a unhandledRequest
method with the HTTP verb
, path
, and original request
,
object if your server receives a request for a route that doesn't have a handler. By default, this method
will throw an error. You can override this method to supply your own behavior:
const server = new Pretender(function() {
// no routes
});
server.unhandledRequest = function(verb, path, request) {
console.log("what is this I don't even...");
}
$.getJSON("/these/arent/the/droids");
Requests set to be handled by pass-through will trigger the passthroughRequest
hook:
const server = new Pretender(function() {
this.get('/some/path', this.passthrough);
});
server.passthroughRequest = function(verb, path, request) {
console.log('request ' + path + ' successfully sent for passthrough');
}
Your server will call a erroredRequest
method with the HTTP verb
, path
, original request
,
and the original error
object if your handler code causes an error.
By default, this will augment the error message with some information about which handler caused the error and then throw the error again. You can override this method to supply your own behavior:
const server = new Pretender(function() {
this.get('/api/songs', request => {
undefinedWAT("this is no function!");
});
});
server.erroredRequest = function(verb, path, request, error) {
SomeTestFramework.failTest();
console.warn("There was an error", error);
}
Pretender is response format neutral, so you normally need to supply a string body as the third part of a response:
this.get('/api/songs', request => {
return [200, {}, "{'id': 12}"];
});
This can become tiresome if you know, for example, that all your responses are
going to be JSON. The body of a response will be passed through a
prepareBody
hook before being passed to the fake response object.
prepareBody
defaults to an empty function, but can be overridden:
const server = new Pretender(function() {
this.get('/api/songs', request => {
return [200, {}, {id: 12}];
});
});
server.prepareBody = function(body){
return body ? JSON.stringify(body) : '{"error": "not found"}';
}
Response headers can be mutated for the entire service instance by implementing a
prepareHeaders
method:
const server = new Pretender(function() {
this.get('/api/songs', request => {
return [200, {}, '{"id": 12}'];
});
});
server.prepareHeaders = function(headers){
headers['content-type'] = 'application/javascript';
return headers;
};
Your pretender instance will track handlers and requests on a few array properties.
All handlers are stored on handlers
property and incoming requests will be tracked in one of
three properties: handledRequests
, unhandledRequests
and passthroughRequests
. The handler is also returned from
any verb function. This is useful if you want to build testing infrastructure on top of
pretender and need to fail tests that have handlers without requests.
You can disable tracking requests by passing trackRequests: false
to pretender options.
const server = new Pretender({ trackRequests: false });
Each handler keeps a count of the number of requests is successfully served.
server.get(/* ... */);
const handler = server.handlers[0];
// or
const handler = server.get(/* ... */);
// then
const numberOfCalls = handler.numberOfCalls;
When you're done mocking, be sure to call shutdown()
to restore the native XMLHttpRequest object:
const server = new Pretender(function() {
... routing ...
});
server.shutdown(); // all done.
yarn test
runs tests onceyarn test:server
runs and reruns on changesIn order to have a more open and welcoming community this project adheres to a code of conduct adapted from the contributor covenant.
Please adhere to this code of conduct in any interactions you have with this project's community. If you encounter someone violating these terms, please let a maintainer (@trek) know and we will address it as soon as possible.
FAQs
Pretender is a mock server library for XMLHttpRequest and Fetch, that comes with an express/sinatra style syntax for defining routes and their handlers.
The npm package pretender receives a total of 263,764 weekly downloads. As such, pretender popularity was classified as popular.
We found that pretender demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.
Security News
Sonar’s acquisition of Tidelift highlights a growing industry shift toward sustainable open source funding, addressing maintainer burnout and critical software dependencies.