Git Product home page Git Product logo

fakerest's Introduction

FakeRest Build Status

Intercept AJAX calls to fake a REST server based on JSON data. Use it on top of Sinon.js (for XMLHTTPRequest) or fetch-mock (for fetch) to test JavaScript REST clients on the browser side (e.g. single page apps) without a server.

Usage

Fake XMLHTTPRequest

<script src="/path/to/FakeRest.min.js"></script>
<script src="/path/to/sinon.js"></script>
<script type="text/javascript">
var data = {
    'authors': [
        { id: 0, first_name: 'Leo', last_name: 'Tolstoi' },
        { id: 1, first_name: 'Jane', last_name: 'Austen' }
    ],
    'books': [
        { id: 0, author_id: 0, title: 'Anna Karenina' },
        { id: 1, author_id: 0, title: 'War and Peace' },
        { id: 2, author_id: 1, title: 'Pride and Prejudice' },
        { id: 3, author_id: 1, title: 'Sense and Sensibility' }
    ],
    'settings': {
        language: 'english',
        preferred_format: 'hardback',
    }
};
// initialize fake REST server
var restServer = new FakeRest.Server();
restServer.init(data);

// use sinon.js to monkey-patch XmlHttpRequest
var server = sinon.fakeServer.create();
server.respondWith(restServer.getHandler());
</script>

Fake fetch

import fetchMock from 'fetch-mock';
import FakeRest from 'fakerest';
var data = {
    'authors': [
        { id: 0, first_name: 'Leo', last_name: 'Tolstoi' },
        { id: 1, first_name: 'Jane', last_name: 'Austen' }
    ],
    'books': [
        { id: 0, author_id: 0, title: 'Anna Karenina' },
        { id: 1, author_id: 0, title: 'War and Peace' },
        { id: 2, author_id: 1, title: 'Pride and Prejudice' },
        { id: 3, author_id: 1, title: 'Sense and Sensibility' }
    ],
    'settings': {
        language: 'english',
        preferred_format: 'hardback',
    }
};
const restServer = new FakeRest.FetchServer('http://localhost:3000');
restServer.init(data);
fetchMock.mock('begin:http://localhost:3000', restServer.getHandler());

FakeRest will now intercept every XmlHttpRequest to the REST server. The handled routes for collections of items are:

GET    /:resource
POST   /:resource
GET    /:resource/:id
PUT    /:resource/:id
PATCH  /:resource/:id
DELETE /:resource/:id

The handled routes for single items are:

GET    /:resource
PUT    /:resource
PATCH  /:resource

Let's see an example:

// Query the fake REST server
var req = new XMLHttpRequest();
req.open("GET", "/authors", false);
req.send(null);
console.log(req.responseText);
// [
//    {"id":0,"first_name":"Leo","last_name":"Tolstoi"},
//    {"id":1,"first_name":"Jane","last_name":"Austen"}
// ]

var req = new XMLHttpRequest();
req.open("GET", "/books/3", false);
req.send(null);
console.log(req.responseText);
// {"id":3,"author_id":1,"title":"Sense and Sensibility"}

var req = new XMLHttpRequest();
req.open("GET", "/settings", false);
req.send(null);
console.log(req.responseText);
// {"language:"english","preferred_format":"hardback"}

var req = new XMLHttpRequest();
req.open("POST", "/books", false);
req.send(JSON.stringify({ author_id: 1, title: 'Emma' }));
console.log(req.responseText);
// {"author_id":1,"title":"Emma","id":4}

// restore native XHR constructor
server.restore();

Tip: The fakerServer provided by Sinon.js is available as a standalone library, without the entire stubbing framework. Simply add the following bower dependency:

devDependencies: {
  "sinon-server": "http://sinonjs.org/releases/sinon-server-1.14.1.js"
}

Installation

FakeRest is available through npm and Bower:

# If you use Bower
bower install fakerest --save-dev
# If you use npm
npm install fakerest --save-dev

REST Flavor

FakeRest defines a REST flavor, described below. It is inspired by commonly used ways how to handle aspects like filtering and sorting.

  • GET /foo returns a JSON array. It accepts three query parameters: filter, sort, and range. It responds with a status 200 if there is no pagination, or 206 if the list of items is paginated. The response contains a mention of the total count in the Content-Range header.

      GET /books?filter={"author_id":1}&embed=["author"]&sort=["title","desc"]&range=[0-9]
    
      HTTP 1.1 200 OK
      Content-Range: items 0-1/2
      Content-Type: application/json
      [
        { "id": 3, "author_id": 1, "title": "Sense and Sensibility", "author": { "id": 1, "first_name": "Jane", "last_name": "Austen" } },
        { "id": 2, "author_id": 1, "title": "Pride and Prejudice", "author": { "id": 1, "first_name": "Jane", "last_name": "Austen" } }
      ]
    

    The filter param must be a serialized object literal describing the criteria to apply to the search query.

      GET /books?filter={"author_id":1} // return books where author_id is equal to 1
      HTTP 1.1 200 OK
      Content-Range: items 0-1/2
      Content-Type: application/json
      [
        { "id": 2, "author_id": 1, "title": "Pride and Prejudice" },
        { "id": 3, "author_id": 1, "title": "Sense and Sensibility" }
      ]
    
      // array values are possible
      GET /books?filter={"id":[2,3]} // return books where id is in [2,3]
      HTTP 1.1 200 OK
      Content-Range: items 0-1/2
      Content-Type: application/json
      [
        { "id": 2, "author_id": 1, "title": "Pride and Prejudice" },
        { "id": 3, "author_id": 1, "title": "Sense and Sensibility" }
      ]
    
      // use the special "q" filter to make a full-text search on all text fields
      GET /books?filter={"q":"and"} // return books where any of the book properties contains the string 'and'
    
      HTTP 1.1 200 OK
      Content-Range: items 0-2/3
      Content-Type: application/json
      [
        { "id": 1, "author_id": 0, "title": "War and Peace" },
        { "id": 2, "author_id": 1, "title": "Pride and Prejudice" },
        { "id": 3, "author_id": 1, "title": "Sense and Sensibility" }
      ]
    
      // use _gt, _gte, _lte, _lt, or _neq suffix on filter names to make range queries
      GET /books?filter={"price_lte":20} // return books where price is less than or equal to 20
      GET /books?filter={"price_gt":20} // return books where price is greater than 20
    
      // when the filter object contains more than one property, the criteria combine with an AND logic
      GET /books?filter={"published_at_gte":"2015-06-12","published_at_lte":"2015-06-15"} // return books published between two dates
    

    The embed param sets the related objects or collections to be embedded in the response.

      // embed author in books
      GET /books?embed=["author"]
      HTTP 1.1 200 OK
      Content-Range: items 0-3/4
      Content-Type: application/json
      [
          { "id": 0, "author_id": 0, "title": "Anna Karenina", "author": { "id": 0, "first_name": "Leo", "last_name": "Tolstoi" } },
          { "id": 1, "author_id": 0, "title": "War and Peace", "author": { "id": 0, "first_name": "Leo", "last_name": "Tolstoi" } },
          { "id": 2, "author_id": 1, "title": "Pride and Prejudice", "author": { "id": 1, "first_name": "Jane", "last_name": "Austen" } },
          { "id": 3, "author_id": 1, "title": "Sense and Sensibility", "author": { "id": 1, "first_name": "Jane", "last_name": "Austen" } }
      ]
    
      // embed books in author
      GET /authors?embed=["books"]
      HTTP 1.1 200 OK
      Content-Range: items 0-1/2
      Content-Type: application/json
      [
          { id: 0, first_name: 'Leo', last_name: 'Tolstoi', books: [{ id: 0, author_id: 0, title: 'Anna Karenina' }, { id: 1, author_id: 0, title: 'War and Peace' }] },
          { id: 1, first_name: 'Jane', last_name: 'Austen', books: [{ id: 2, author_id: 1, title: 'Pride and Prejudice' }, { id: 3, author_id: 1, title: 'Sense and Sensibility' }] }
      ]
    
      // you can embed several objects
      GET /authors?embed=["books","country"]
    

    The sort param must be a serialized array literal defining first the property used for sorting, then the sorting direction.

      GET /author?sort=["date_of_birth","asc"]  // return authors, the oldest first
      GET /author?sort=["date_of_birth","desc"]  // return authors, the youngest first
    

    The range param defines the number of results by specifying the rank of the first and last result. The first result is #0.

      GET /books?range=[0-9] // return the first 10 books
      GET /books?range=[10-19] // return the 10 next books
    
  • POST /foo returns a status 201 with a Location header for the newly created resource, and the new resource in the body.

      POST /books
      { "author_id": 1, "title": "Emma" }
    
      HTTP 1.1 201 Created
      Location: /books/4
      Content-Type: application/json
      { "author_id": 1, "title": "Emma", "id": 4 }
    
  • GET /foo/:id returns a JSON object, and a status 200, unless the resource doesn't exist

      GET /books/2
    
      HTTP 1.1 200 OK
      Content-Type: application/json
      { "id": 2, "author_id": 1, "title": "Pride and Prejudice" }
    

    The embed param sets the related objects or collections to be embedded in the response.

      GET /books/2?embed=['author']
    
      HTTP 1.1 200 OK
      Content-Type: application/json
      { "id": 2, "author_id": 1, "title": "Pride and Prejudice", "author": { "id": 1, "first_name": "Jane", "last_name": "Austen" } }
    
  • PUT /foo/:id returns the modified JSON object, and a status 200, unless the resource doesn't exist

  • DELETE /foo/:id returns the deleted JSON object, and a status 200, unless the resource doesn't exist

If the REST flavor you want to simulate differs from the one chosen for FakeRest, no problem: request and response interceptors will do the conversion (see below).

Note that all of the above apply only to collections. Single objects respond to GET /bar, PUT /bar and PATCH /bar in a manner identical to those operations for /foo/:id, including embedding. POST /bar and DELETE /bar are not enabled.

Supported Filters

Operators are specified as suffixes on each filtered field. For instance, applying the _lte operator on the price field for the books resource is done by like this:

GET /books?filter={"price_lte":20} // return books where price is less than or equal to 20
  • _eq: check for equality on simple values:

      GET /books?filter={"price_eq":20} // return books where price is equal to 20
    
  • _neq: check for inequality on simple values

      GET /books?filter={"price_neq":20} // return books where price is not equal to 20
    
  • _eq_any: check for equality on any passed values

      GET /books?filter={"price_eq_any":[20, 30]} // return books where price is equal to 20 or 30
    
  • _neq_any: check for inequality on any passed values

      GET /books?filter={"price_neq_any":[20, 30]} // return books where price is not equal to 20 nor 30
    
  • _inc_any: check for items that includes any of the passed values

      GET /books?filter={"authors_inc_any":['William Gibson', 'Pat Cadigan']} // return books where authors includes either 'William Gibson' or 'Pat Cadigan' or both
    
  • _q: check for items that contains the provided text

      GET /books?filter={"author_q":['Gibson']} // return books where author includes 'Gibson' not considering the other fields
    
  • _lt: check for items that has a value lower than the provided value

      GET /books?filter={"price_lte":100} // return books that have a price lower that 100
    
  • _lte: check for items that has a value lower or equal than the provided value

      GET /books?filter={"price_lte":100} // return books that have a price lower or equal to 100
    
  • _gt: check for items that has a value greater than the provided value

      GET /books?filter={"price_gte":100} // return books that have a price greater that 100
    
  • _gte: check for items that has a value greater or equal than the provided value

      GET /books?filter={"price_gte":100} // return books that have a price greater or equal to 100
    

Usage and Configuration

// initialize a rest server with a custom base URL
var restServer = new FakeRest.Server('http://my.custom.domain'); // // only URLs starting with my.custom.domain will be intercepted
restServer.toggleLogging(); // logging is off by default, enable it to see network calls in the console
// Set all JSON data at once - only if identifier name is 'id'
restServer.init(json);
// modify the request before FakeRest handles it, using a request interceptor
// request is {
//     url: '...',
//     headers: [...],
//     requestBody: '...',
//     json: ..., // parsed JSON body
//     queryString: '...',
//     params: {...} // parsed query string
// }
restServer.addRequestInterceptor(function(request) {
    var start = (request.params._start - 1) || 0;
    var end = request.params._end !== undefined ? (request.params._end - 1) : 19;
    request.params.range = [start, end];
    return request; // always return the modified input
});
// modify the response before FakeRest sends it, using a response interceptor
// response is {
//     status: ...,
//     headers: [...],
//     body: {...}
// }
restServer.addResponseInterceptor(function(response) {
    response.body = { data: response.body, status: response.status };
    return response; // always return the modified input
});
// set default query, e.g. to force embeds or filters
restServer.setDefaultQuery(function(resourceName) {
    if (resourceName == 'authors') return { embed: ['books'] }
    if (resourceName == 'books') return { filter: { published: true } }
    return {};
})
// enable batch request handler, i.e. allow API clients to query several resourecs into a single request
// see [Facebook's Batch Requests philosophy](https://developers.facebook.com/docs/graph-api/making-multiple-requests) for more details.
restServer.setBatchUrl('/batch');

// you can create more than one fake server to listen to several domains
var restServer2 = new FakeRest.Server('http://my.other.domain');
// Set data collection by collection - allows to customize the identifier name
var authorsCollection = new FakeRest.Collection([], '_id');
authorsCollection.addOne({ first_name: 'Leo', last_name: 'Tolstoi' }); // { _id: 0, first_name: 'Leo', last_name: 'Tolstoi' }
authorsCollection.addOne({ first_name: 'Jane', last_name: 'Austen' }); // { _id: 1, first_name: 'Jane', last_name: 'Austen' }
// collections have autoincremented identifier but accept identifiers already set
authorsCollection.addOne({ _id: 3, first_name: 'Marcel', last_name: 'Proust' }); // { _id: 3, first_name: 'Marcel', last_name: 'Proust' }
restServer2.addCollection('authors', authorsCollection);
// collections are mutable
authorsCollection.updateOne(1, { last_name: 'Doe' }); // { _id: 1, first_name: 'Jane', last_name: 'Doe' }
authorsCollection.removeOne(3); // { _id: 3, first_name: 'Marcel', last_name: 'Proust' }

var server = sinon.fakeServer.create();
server.autoRespond = true;
server.respondWith(restServer.getHandler());
server.respondWith(restServer2.getHandler());

Development

# Install dependencies
make install
# Watch source files and recompile dist/FakeRest.js when anything is modified
make watch
# Run tests
make test
# Build minified version
make build

License

FakeRest is licensed under the MIT Licence, sponsored by marmelab.

fakerest's People

Contributors

btyoung avatar dependabot[bot] avatar djhi avatar fzaninotto avatar georgiosgiatsidis avatar jeromemacias avatar manuquentin avatar patrickp-at-work avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fakerest's Issues

Example code doesn't work

<script src="/path/to/FakeRest.min.js"></script>
<script src="/path/to/sinon.js"></script>
<script type="text/javascript">
var data = {
    'authors': [
        { id: 0, first_name: 'Leo', last_name: 'Tolstoi' },
        { id: 1, first_name: 'Jane', last_name: 'Austen' }
    ],
    'books': [
        { id: 0, author_id: 0, title: 'Anna Karenina' },
        { id: 1, author_id: 0, title: 'War and Peace' },
        { id: 2, author_id: 1, title: 'Pride and Prejudice' },
        { id: 3, author_id: 1, title: 'Sense and Sensibility' }
    ],
    'settings': {
        language: 'english',
        preferred_format: 'hardback',
    }
};
// initialize fake REST server
var restServer = new FakeRest.Server();
restServer.init(data);
// use sinon.js to monkey-patch XmlHttpRequest
var server = sinon.fakeServer.create();
server.respondWith(restServer.getHandler());
</script>

throws "FakeRest.min.js:1 Uncaught Error: Can't initialize a Collection with anything else than an array of items"

using via bower and sinon installed via:
devDependencies: { "sinon-server": "http://sinonjs.org/releases/sinon-server-1.14.1.js" }

Minified version uses eval which forces to use a weak Content Security Policy (CSP)

We've noticed that the minified build of FakeRest uses eval in some places, for example:

...Function("binder","return function ("+c.join(",")+"){ return binder.apply(this,arguments); }")(a);

The above fails if the Content Security Policy does not allow unsafe-eval for the script-src attribute.
The error (in Chrome): EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self".

Allowing 'unsafe-eval' for scripts is considered very risky from a security point of view.

Could you create a distribution that does not rely on eval so that we can use FakeRest with a strong CSP?

Whitelist URLs to not intercept?

I have set up fakerest with
fetchMock.mock('^http://fakeapi', restServer.getHandler())

Now when I want to test my auth api, I have a request going out fetch(request) with request being:
Request {method: "POST", url: "http://localhost:4040/v1/auth/login", ...

Unfortunately the request does not go out but gets intercepted and results in the error:

Uncaught Error: No fallback response defined for GET to [object Request]
    at FetchMock.webpackJsonp../node_modules/fetch-mock/es5/fetch-mock.js.FetchMock.fetchMock (fetch-mock.js:104)
    at eval (eval at webpackJsonp../src/components/menu/authentication/authClient.js.__webpack_exports__.a (authClient.js:13), <anonymous>:1:1)
    at webpackJsonp../src/components/menu/authentication/authClient.js.__webpack_exports__.a (authClient.js:13)
    at runCallEffect (proc.js:503)
    at runEffect (proc.js:425)
    at next (proc.js:306)
    at currCb (proc.js:378)
    at proc.js:489
    at exec (scheduler.js:19)
    at flush (scheduler.js:60)

Is there a way to let requests to a specific host through?

Problem to intercept jQuery ajax requests (and also Angular $http requests)

Here is how I init (data is a simple hash):

          var restServer = new FakeRest.Server();
          restServer.init(data);
          var server = sinon.fakeServer.create();
          server.respondWith(restServer.getHandler());

Using pure JS works as expected:

          var req = new XMLHttpRequest();
          req.onreadystatechange = function() {
            console.log(req.responseText);
          };
          req.open("GET", "/authors", false);
          req.setRequestHeader( 'Accept', '*/*' );
          req.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest');
          req.send(null);

If I try with jQuery the request seems to be caught but I get no response:

          var jqxhr = jQuery.ajax( { url: "/authors", accepts: { all: '*/*' } } )
          .done(function() {
            console.log( "success" );
          })
          .fail(function() {
            console.log( "error" );
          })
          .always(function() {
            console.log( "complete" );
          });

No console output.

The same with angularJS:

            $http({
              method: 'GET',
              url: '/authors'
            }).then(function successCallback(response) {
              console.log( 'ok' );
            }, function errorCallback(response) {
              console.log( 'err' );
            });

Any idea?

Digest authentication

Hello!
It is a very useful application. Are you planning to implement authentication? In particular, digital authentication?

REST flavor

FakeRest uses a standard REST flavor, described below.

Can you clarify in your docs where this flavor came from? Was it invented by marmelab? Because if it is a common standard also used elsewhere, it would be useful to link it

fakerest using babel-runtime v6 causing deprecation warning using core-js v2

Using latest ra-data-fakerest version 4.16.12 while running npm i there is a npm warning:

npm ls returns
├─┬ [email protected]
│ ├─┬ [email protected]
│ │ ├─┬ [email protected]
│ │ │ ├── [email protected]
│ │ │ └── [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped

npm WARN deprecated [email protected]: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.

While babel 7 does not depend on core-js anymore, is there a chance to upgrade fakerest using babel v7?

FetchServer No headers on Response object

I noticed that headers are somehow not passed to Response object.

I have a simple setup like this

const restServer = new FakeRest.FetchServer('http://localhost:3000');
restServer.toggleLogging();
restServer.init(data);
restServer.addResponseInterceptor(function (response) {
  console.log('Response interceptor', response);
  return response;
});
fetchMock.mock('begin:http://localhost:3000', restServer.getHandler());


// and somewhere in the code
fetch('http://localhost:3000/scans?' + queryString, { headers })
    .then(res => {
      console.log('fetch response headers', res.headers);
    });

FetchServer log shows correct headers.

image

I tried with fetchMock version 5 and the newest 9, same results.

Tested on Chrome and Firefox.

ng-admin example

<script src="bower_components/sinon-server/index.js" type="text/javascript"></script>

Please I want to know what is index.js file ??
and when I add this <script> tag it's not working
so where is the problem ,please?

Validation interceptors

Hi, it would be nice to allow, parallel to the declaration of data, the declaration of a filter for each data block, to allow the simulation of validation errors and things like this:

restServer.initInterceptors({
    'customers': [
        { post: function(xhr,data,defaultHandler){
                       if(data.id < 0){
                              return {status:500,body:"WrongId"};
                      }
                      return defaultHandler(xhr,data)
        }}
    ]
});

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.