Coder Social home page Coder Social logo

connect-api-mocker's Introduction

connect-api-mocker

Build Status npm version codecov semantic-release

connect-api-mocker is a connect.js middleware that fakes REST API server with filesystem. It will be helpful when you try to test your application without the actual REST API server.

It works with a wide range of servers: connect, express, browser-sync, lite-server, webpack-dev-server. Also it can be used as a command line tool with the help of cli-api-mocker.

Detailed article: https://medium.com/@muratcorlu/mocking-rest-endpoints-in-web-apps-easy-way-d4cd0e9db000

A presentation at AmsterdamJS'18 conference: https://www.youtube.com/watch?v=yF_8O4l-Ybc

Türkçe sunum için: https://www.youtube.com/watch?v=cVL8sesauCU

Installation

npm install connect-api-mocker --save-dev

Usage

Using with Connect

var http = require('http');
var connect = require('connect');
var apiMocker = require('connect-api-mocker');

var app = connect();

app.use('/api', apiMocker('mocks/api'));

http.createServer(app).listen(8080);

Using with Express

var express = require('express');
var apiMocker = require('connect-api-mocker');

var app = express();

app.use('/api', apiMocker('mocks/api'));

app.listen(8080);

Using with BrowserSync

var browserSync = require('browser-sync').create();
var apiMocker = require('connect-api-mocker');

var restMock = apiMocker('/api', 'mocks/api');

browserSync.init({
  server: {
    baseDir: './',
    middleware: [
      restMock,
    ],
  },
  port: 8080,
});

Using with lite-server

bs-config.js file:

var apiMocker = require('connect-api-mocker');

var restMock = apiMocker('/api', 'mocks/api');

module.exports = {
  server: {
    middleware: {
      // Start from key `10` in order to NOT overwrite the default 2 middleware provided
      // by `lite-server` or any future ones that might be added.
      10: restMock,
    },
  },
  port: 8080,
};

Using with Grunt

You can use it with Grunt. After you install grunt-contrib-connect add api-mocker middleware to your grunt config. The mocks/api folder will be served as REST API at /api.

module.exports = function(grunt) {
  var apiMocker = require('connect-api-mocker');

  grunt.loadNpmTasks('grunt-contrib-connect');  // Connect - Development server

  // Project configuration.
  grunt.initConfig({

    // Development server
    connect: {
      server: {
        options: {
          base: './build',
          port: 9001,
          middleware: function(connect, options) {

            var middlewares = [];

            // mock/rest directory will be mapped to your fake REST API
            middlewares.push(apiMocker(
                '/api',
                'mocks/api'
            ));

            // Static files
            middlewares.push(connect.static(options.base));
            middlewares.push(connect.static(__dirname));

            return middlewares;
          }
        }
      }
    }
  });
}

After you can run your server with grunt connect command. You will see /api will be mapped to mocks/api.

Using with Webpack

To use api mocker on your Webpack projects, simply add a setup options to your webpack-dev-server options:

devServer.setup , This option is deprecated in favor of before and will be removed in v3.0.0.

  ...
  before: function(app) {
    app.use(apiMocker('/api', 'mocks/api'));
  },
  ...

Using with CRA

To use api mocker on your cra projects, please install customize-cra and react-app-rewired using npm to modify webpack config file:

npm install customize-cra react-app-rewired --save-dev

Then, create a file named config-overrides.js, override webpack config using below codes:

  const apiMocker = require("connect-api-mocker"),
    { overrideDevServer } = require("customize-cra");

  const devServerConfig = () => config => {
    return {
        ...config,
        before: (app,server)=> {
            //call cra before function to not break code
            config.before(app, server);
            //Then add our mocker url and folder 
            app.use(apiMocker('/api', 'mocks/api'));
        } 
    }
  }

  module.exports = {
    devServer: overrideDevServer(
      devServerConfig()
    )
  };

Finally, change our run method to from "react-scripts start" to "react-app-rewired start" in package.json file:

...
"scripts": {
    "start": "react-app-rewired start",
    ...
  }
  ...

Using with other languages other than JavaScript

If you have a Python/Ruby/.NET etc. project and want to use that mocking functionality, you can use cli-api-mocker as a wrapper of connect-api-mocker for command line. With the help of cli-api-mocker, if you run mockit command, you will have a seperate web server that will handle your mocks as a REST API. Please look for cli-api-mocker readme for details.

Directory Structure

You need to use service names as directory name and http method as filename. Middleware will match url to directory structure and respond with the corresponding http method file.

Example REST service: GET /api/messages

Directory Structure:

_ api
  \_ messages
     \_ GET.json

Example REST service: GET /api/messages/1

Directory Structure:

_ api
  \_ messages
     \_ 1
        \_ GET.json

Example REST service: POST /api/messages/1

Directory Structure:

_ api
  \_ messages
     \_ 1
        \_ POST.json

Example REST service: DELETE /api/messages/1

Directory Structure:

_ api
  \_ messages
     \_ 1
        \_ DELETE.json

Custom responses

If you want define custom responses you can use js files with a middleware function that handles requests.

Example REST service: POST /api/messages

Directory Structure:

_ api
  \_ messages
     \_ POST.js

POST.js file:

module.exports = function (request, response) {
  if (!request.get('X-Auth-Key')) {
    response.status(403).send({});
  } else {
    response.sendFile('POST.json', {root: __dirname});
  }
}

POST.js file for non ExpressJS server:

const fs = require('fs');
const path = require('path');

module.exports = (request, response) => {
  if (!request.get('X-Auth-Key')) {
    response.statusCode = 403;
    response.end();
  } else {
    const filePath = path.join(__dirname, 'POST.json');
    const stat = fs.statSync(filePath);

    response.writeHead(200, {
        'Content-Type': 'application/json',
        'Content-Length': stat.size
    });

    const readStream = fs.createReadStream(filePath);
    // We replaced all the event handlers with a simple call to readStream.pipe()
    readStream.pipe(response);
  }
}

Another Example: Respond different json files based on a query parameter:

  • Request to /users?type=active will be responded by mocks/users/GET_active.json
  • Request to /users will be responded by mocks/users/GET.json

GET.js file:

const fs = require('fs');
const path = require('path');

module.exports = function (request, response) {
  let targetFileName = 'GET.json';

  // Check is a type parameter exist
  if (request.query.type) {
    // Generate a new targetfilename with that type parameter
    targetFileName = 'GET_' + request.query.type + '.json';
  }
  const filePath = path.join(__dirname, targetFileName);
  // If file does not exist then respond with 404 header
  try {
    fs.accessSync(filePath);
  }
  catch (err) {
    return response.status(404);
  }
  // Respond with filePath
  response.sendFile(filePath);
}

GET.js file for non ExpressJS server:

const url =  require('url');
const fs = require('fs');
const path = require('path');

module.exports = function (request, response) {
  let targetFileName = 'GET.json';
  const typeQueryParam = url.parse(request.url, true).query.type;
  // Check is a type parameter exist
  if (typeQueryParam) {
    // Generate a new targetfilename with that type parameter
    targetFileName = 'GET_' + typeQueryParam + '.json';
  }

  var filePath = path.join(__dirname, targetFileName);

  // If file does not exist then respond with 404 header
  try {
    fs.accessSync(filePath);
  }
  catch (err) {
    response.statusCode = 404;
    response.end();
    return;
  }

  const stat = fs.statSync(filePath);
  response.writeHead(200, {
      'Content-Type': 'application/json',
      'Content-Length': stat.size
  });

  const readStream = fs.createReadStream(filePath);
  // We replaced all the event handlers with a simple call to readStream.pipe()
  readStream.pipe(response);
}

Helper functions for custom responses

Connect-Api-Mocker also presents a bunch of helper functions to speed up writing simple custom responses. There are:

  • status(statusCode): Set status code of response
  • notFound(message?): Set status code as 404 and optionally sends message
  • created(): Sets status code as 201
  • success(): Sets status code as 200
  • delay(duration): Delays the request by given duration(in ms).
  • json(data|callback(req,res)): Send given JSON object as response.
  • file(filePath): Responds with the content of file in given path(full path)
  • type(contentType): Sets content-type header.
  • end(body): Ends request and optionally sends the string output

You can use these functions in custom responses, like:

const { notFound } = require('connect-api-mocker/helpers');

module.exports = notFound('Page is not found');

Also you can combine multiple functions:

const { delay, created, json } = require('connect-api-mocker/helpers');

module.exports = [delay(500), created(), json({success: true})];

json middleware also accepts a callback that has request and response objects as parameters:

const { json } = require('connect-api-mocker/helpers');

module.exports = [json(req => ({
  id: req.params.userId,
  success: true
}))];

Another example to return image as response:

const { type, file } = require('connect-api-mocker/helpers');

// Assuming a file named GET.png exists next to this file
const filePath = path.join(__dirname, './GET.png');

module.exports = [type('image/png'), file(filePath)];

Wildcards in requests

You can use wildcards for paths to handle multiple urls(like for IDs). If you create a folder structure like api/users/__user_id__/GET.js, all requests like /api/users/321 or /api/users/1 will be responded by custom middleware that defined in your GET.js. Also id part of the path will be passed as a request parameter named as user_id to your middleware. So you can write a middleware like that:

api/users/__user_id__/GET.js file:

module.exports = function (request, response) {
  response.json({
    id: request.params.user_id
  });
}

You can also define ANY.js or ANY.json files that catch all methods.

api/users/__user_id__/ANY.js file:

module.exports = function (request, response) {
  response.json({
    id: request.params.user_id,
    method: request.method
  });
}

XML Support

Api Mocker also can handle XML responses. As you can see, for custom responses, it's not an issue. Because you are completely free about responses in custom responses. But for simple mocks, api mocker try to find a json file by default. You can set that behaviour as type in api mocker configuration:

app.use('/user-api', apiMocker({
  target: 'other/target/path',
  type: 'xml'
}));

If you use xml as type, api mocker should look for mocks/users/GET.xml file for a request to /users. Also you can use auto for type:

app.use('/user-api', apiMocker({
  target: 'other/target/path',
  type: 'auto'
}));

In that case, api mocker will look for Accept header in the request to determine response format. So, if you make a request with a Accept: application/json header, it'll try to send a response with a json file. If you make a request with a Accept: application/xml header, it'll try to send a response with an xml file.

Defining multiple mock configurations

You can use apiMocker multiple times with your connect middleware server. In example below, we are defining 3 mock server for 3 different root paths:

app.use('/api/v1', apiMocker('target/path'));
app.use('/user-api', apiMocker({
  target: 'other/target/path'
}));
app.use(apiMocker('/mobile/api', {
  target: 'mocks/mobile'
});

Next on not found option

If you have some other middlewares that handles same url(a real server proxy etc.) you can set nextOnNotFound option to true. In that case, api mocker doesnt trigger a 404 error and pass request to next middleware. (default is false)

apiMocker('/api', {
  target: 'mocks/api',
  nextOnNotFound: true
});

With that option, you can mock only specific urls simply.

Body parser

By default request body is pre-processed with body-parser. Default body-parser configuration uses JSON parser. Example belows configures usage of json (default) parser. In order to disable default pre-processing set bodyParser option to false.

apiMocker('/text', {
  target: 'test/mocks',
  bodyParser: false
})

In order to modify default body-parser behaviour use bodyParser object. bodyParser object supports configuration of

  • parser type via type setting.
  • parser options via options setting.

Supported parsers and corresponding options can be found here

Example belows configures usage of text parser for requests with content-type=application/vnd.custom-type

apiMocker('/text', {
  target: 'test/mocks',
  bodyParser: {
    type: 'text',
    options: { type: 'application/vnd.custom-type' }
  }
})

Logging

If you want to see which requests are being mocked, set the verbose option either to true or provide your own function.

apiMocker('/api', {
  target: 'mocks/api',
  verbose: ({ req, filePath, fileType }) => console.log(`Mocking endpoint ${req.originalUrl} using ${filePath}.${fileType}.`)
});

connect-api-mocker's People

Contributors

daanoz avatar dependabot[bot] avatar ertug avatar eser avatar fatihhayri avatar greenkeeper[bot] avatar iakovmarkov avatar jaywcjlove avatar khelkun avatar muratcorlu avatar olegdovger avatar ozanturksever avatar sercaneraslan avatar valeriiudodov avatar valoricde avatar zaycker 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

connect-api-mocker's Issues

[Feature Request] Enable logging

Hey @muratcorlu
It would be nice if you could turn on logging, displaying informations like the request's url and what happend with it (json file loaded/ forwarded because no mock was found etc)

Custom response helper methods

Providing some helper functions for custom responses would be great, like:

const { jsonResponse } = require('connect-api-mocker/helpers');

module.exports = jsonResponse({success: true});

or

const { delay, jsonResponse } = require('connect-api-mocker/helpers');

module.exports = [delay(200), jsonResponse({success: true})];

A list of some helper functions could be:

  • delay(milliseconds) equivalent to setTimeout(next, milliseconds);
  • jsonResponse(data) equivalent to res).json(data);
  • success() equivalent to res.status(200
  • notFound() equivalent to res.status(404)
  • created() equivalent to res.status(201)
  • send() equivalent to res.end()
    ...

At the and that could be possible and helpful:

const { delay, created, send } = require('connect-api-mocker/helpers');

module.exports = [delay(1000), created(), send()];

Update(or add) express.js independent examples

Now most of our custom response examples are express.js dependent. And some of our officially supported implementations(like lite-server) don't use express. So updating or adding some examples that will work within all type of servers will be great.

put.js behaves slow(10s+)

Under agreement folder that is properly mapped with connect-api-mocker and webpack, I have one dynamic response, post and put...something like this:

  • agreement
    • __agreementId__
      • GET.js
    • POST.js
    • PUT.js

Everything works fine but for some reason mapping of the PUT.js is too slow, it takes sometime more than 10s for server to respond...I am saying on purpose mapping because I have added logs into a put function and they are not even hit for that 10s, so process before it is taking it too long, not sure why...

any ideas?

p.s. also I have tried to remove everything we have in put and directly return 204, still same behavior

Facing cors issue

Facing cors issue. I am running a CRA on 3000 and mockapi on 8080 (with express.js) and getting cors issue.

Access to XMLHttpRequest at 'http://localhost:8080/api/orders/inspection/1021455197' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I am also using a proxy option in my package.json

Undefined baseUrl property when using with Connect

Hi, I'm trying to setup mockers for my app using connect, but I get this error:

TypeError: Cannot read property 'replace' of undefined
    at trimSlashes (/project/node_modules/connect-api-mocker/api-mocker.js:29:15)
    at /project/node_modules/connect-api-mocker/api-mocker.js:85:15
    at call (/project/node_modules/connect/index.js:239:7)
    at next (/project/node_modules/connect/index.js:183:5)
    at next (/project/node_modules/connect/index.js:161:14)
    at SendStream.error (/project/node_modules/serve-static/index.js:121:7)
    at SendStream.emit (events.js:159:13)
    at SendStream.error (/project/node_modules/serve-static/node_modules/send/index.js:270:17)
    at SendStream.onStatError (/project/node_modules/serve-static/node_modules/send/index.js:421:12)
    at next (/project/node_modules/serve-static/node_modules/send/index.js:764:28)
    at /project/node_modules/serve-static/node_modules/send/index.js:772:23
    at FSReqWrap.oncomplete (fs.js:166:21)

Unless I'm doing something wrong, it seems that the problem is in missing baseUrl property. Which is an Express specific thing. And even with Express it's only set when router is used, so it's still might be undefined.

In my case with Connect request is an IncomingMessage object, which doesn't have such a property.

My setup:

var connect = require('connect');
var app = connect();

var serveStatic = require('serve-static');
app.use(serveStatic(__dirname+'/public', {'index': ['index.html']}));
app.use('/mocks', serveStatic(__dirname+'/app/mocks'));

var apiMocker = require('connect-api-mocker');
app.use('/api', apiMocker('/mocks/api'));

app.listen(3333);

Configuration with Next.js

Hello Murat.

Would you have some documentation or something like that, on how to configure it to be used with next.js

Catch all nested paths

To have a chance to define a mock that will catch all requests that starts with a path could be great.

For example, lets say we have 2 endpoints like below:

/api/users/3123123/messages/321
/api/users/4343/messages
/api/users/213

For now there is no chance to catch all these 3 endpoints with a single custom middleware. Maybe to have a mock definition in a path like below could be useful:

mocks/api/users/__user_id*__/ANY.js

Notice the star char in the wildcard name. That could mean, that wildcard will match all of this paths and nested paths.

Let's think about this...

Verbose mode

Add some more debug logs with an optional parameter

Custom response middleware without `body-parser`

As first discussed here, in version 1.3.5 there is a breaking change (commit) that affects custom response middleware.

body-parser was added before the custom middleware, which means that the request is consumed before it can reach the custom code. This means that the 'data' and 'end' request events are no longer called in the custom code. See this issue for a better explanation.
Maybe revert to the old code (let the response unaltered) and add a config option to enable parsing the JSON body for you?

Returning images as mock

For some use cases, we need to able to return image responses as mocks. Like GET.json or GET.xml, GET.jpeg should be returned for an image request. There are some tricky parts for this:

  • For response type, we have auto type that is checking request content-type header and falls-back to json. But for images we should check mock files for this path in any case for image mock files.
  • Respect to wildcard support(#25), checking all possible mock files will be a bit more complex.
  • We should able to extend this approach for other type of responses. (Video, yaml etc.)

So idea is:

  • If request is GET /path with accept type application/json we should only check path/GET.json
Request method Request path Accept type Mock file to be checked
GET /path application/json path/GET.json
GET /path application/xml path/GET.xml
GET /path image/* path/GET.(jpeg|gif|png...)
GET /path */* path/GET.(json|xml|jpeg|gif|png...)

* Custom middlewares should have priority in any case
** If there is no prefered accept type and if there is a json mock file, it should be prioritised

Multiple path configuration with nextOnNotFound not works

If you have multiple path mapping like that, it doesn't work properly for now:

apiMocker({
    '/projects': {
        target: 'mocks/api/projects',
        nextOnNotFound: true
    },
    '/analytics': {
        target: 'mocks/api/analytics',
        nextOnNotFound: true
    }
});

Workaround for that kind of need is creating multiple middlewares by calling apiMocker multiple. For example(for webpack):

...
setup: function(app) {
    app.use(apiMocker({
        '/projects': {
            target: 'mocks/api/projects',
            nextOnNotFound: true
        }
    });
    app.use(apiMocker({
        '/analytics': {
            target: 'mocks/api/analytics',
            nextOnNotFound: true
        }
    });
}
...

Delaying mock response

Is it possible to delay mock responses to simulate slow networks? I am using webpack:

  devServer: {
    overlay: {
      warnings: true,
      errors: true
    },
    before: (app) => {
      app.use(apiMocker('/api', 'mocks/api'));
    },
  }

[Feature Request] Add wildcard support for json files

Hello,

it would be cool if the wildcard is passed into the json files to, so { id: __project_uuid__ } will return the current uuid.

Maybe the wildcard needs some type of escaping, eg: $__project_uuid__$

This would reduce the usage of more complicated js files.

generate JSON files

hi everyone, I hope I understand correctly, this library does not generate JSON files? correct?

I was starting to develop a library that would allow me to generate JSON files, does something like this already exist?

I am so lucky to have this library in my life.
:)

Can't seem to get anything from request.body with POST

Server.js is as follows

app.use(
  '/api',
  apiMocker('api', {
    target: 'api',
    bodyParser: {
      type: 'urlencoded',
      options: { extended: true }
    },    
    verbose: ({ req, filePath, fileType }) =>
      console.log(
        `Mocking endpoint ${req.originalUrl} using ${filePath}.${fileType}.`
      )
  })
)

POST.js is as follows

module.exports = function (request, response) {
console.log(request.body)
return   response.sendFile('229110161010.json', { root: __dirname })

}

The log I get is is just and empty {}

How do I access the data in the body on my post calls?

Dynamic url parameters support

If we want to mock an endpoint like GET /products/312/images we need to create a GET.json file in folder structure like products/312/images. And we need to clone that files for other product ids like:

products/424/images
products/1/images

Possible feature to simplify that process, create a folder name like products/__product_id__/images and for matching requests, respond with realated json file or passing that product_id parameter to defined middlewares like GET.js or POST.js etc.

Parse FormData request

Hi there,

is it possible to read and parse data from a request that are being sent as FormData?

const formData = new FormData()
formData.append('param1', 'value1')
formData.append('param2', 'value2')

fetch('/api/mock-url', {
  method: 'POST',
  body: formData,
})

And then in api/mock-url/POST.js

module.exports = function (request, response) {
  // ???
}

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on Greenkeeper branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet. We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

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.