百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
P

pretender

> 编程语言
开源

一个带有漂亮路由 DSL 的模拟服务器库

1.3K stars0 点赞2 次浏览
访问官网GitHub

工具介绍

一个带有漂亮路由 DSL 的模拟服务器库

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!

…

Usage

yarn add -D pretender
# or
npm install --save-dev pretender

You can load Pretender directly in the browser.

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(); //  {
        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('/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);

Hooks

Handled Requests

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");

Unhandled Requests

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");

Pass-through Requests

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');
}

Error Requests

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);
}

Mutating the body

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"}';
}

Mutating the headers

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}'];
  })

Issues· 0 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

JavaScript

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言