Code Monkey home page Code Monkey logo

service-mocker's Introduction

Service Mocker

Travis CI CircleCI Coverage Version License

Build Status

Service Mocker is an API mocking framework for frontend developers. With the power of service workers, we can easily set up mocking services without any real servers. It sets developers free from intricate workflows, complex documentations and endless proxies from server to server.

Q: Is Service Worker ready?

A: No, not yet.

Q: Is Service Mocker ready?

A: Yes! Welcome to the future!

Installation

Since you are likely to run Service Mocker only during development, you will need to add service-mocker as a devDependency:

npm install service-mocker --save-dev

For legacy browsers, you may also need the polyfills:

npm install service-mocker-polyfills --save-dev

Features

  • No server is required.
  • Real HTTP requests and responses that can be inspected in modern browsers.
  • express style routing system.
  • IE10+ compatibility.

Hello new world

A typical mocker includes two parts: client and server. First, let's create a server script named server.js:

// server.js
import { createServer } from 'service-mocker/server';

const { router } = createServer();

router.get('/greet', (req, res) => {
  res.send('Hello new world!');
});

// or you can use the shorthand method
router.get('/greet', 'Hello new world!');

Then, we need to write a client script to connect to the server:

// app.js
import 'service-mocker-polyfills';
import { createClient } from 'service-mocker/client';

const client = createClient('path/to/server.js');

client.ready.then(async () => {
  const response = await fetch('/greet');

  console.log(await response.text());
});

After that, create a .html file and include ONLY the client script:

<script src="app.js"></script>

Now navigate your browser to your local dev server (e.g. http://localhost:3000). Open the console and you will see the following messages:

> [mocker:modern] connection established
>
> Hello new world!

Welcome to the future ๐Ÿ‘.

Working with webpack

While using webpack, it's recommended to use sw-loader to create a standalone server script:

import scriptURL from 'sw-loader!path/to/server.js';
import { createClient } from 'service-mocker/client';

const client = createClient(scriptURL);

client.ready.then(...);

Docs & Demos

Team

Dolphin Wood Vincent Bel
Dolphin Wood Vincent Bel

License

MIT

service-mocker's People

Contributors

allenfang avatar armandabric avatar egoist avatar idiotwu avatar vincentbel 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

service-mocker's Issues

Hanging in chrome after reloading

Environment

  • Browser: Chrome 58.0.3029.96/macOS
  • Version of service-mocker: 1.1.1

Expected Behavior

client.ready should be resolved.

Current Behavior

After reloading page, there's a high probability that service worker is hanging on installing state:

image

Steps to Reproduce

  1. Register service mocker
  2. Keep reloading page until hanging

Use in browser directly

Maybe bundle it in a single file in iife format so that we can just importScripts('/path/to/service-mocker/server.js') and use the global createServer variable.

Auto reload with webpack

Environment

  • Browser: Chrome 59
  • Version of service-mocker: 2.0.0

I'm not getting webpack to refresh the ServiceWorker code when I save the file.

Is it intended behavior or am I misconfiguring?

Using sw-loader with the code:

const serverMockUrl = require('sw-loader!server-mock')
const { createClient } = require('service-mocker/client')
createClient(serverMockUrl).ready.then(() => console.log('ready'))

Thanks!

Very cool idea for this package by the way ๐Ÿ˜‰ Keep up the good work!

createClient(scriptURL).ready does not resolve at root /

Environment

  • Browser: Chrome
  • Version of service-mocker: 2.0.1

When I load my app at localhost:3000/users it works fine. However, when I load it at the root localhost:3000, createClient(scriptURL).ready never resolves (or rejects). I'm using Webpack + sw-loader. I can see in the network tab it loads the service worker, and no error in console.

import { createClient } from 'service-mocker/client'
import scriptURL from 'sw-loader?name=mock-api-sw.js!./mock-api-server.js'

const client = createClient(scriptURL).ready
  .then(r => console.log('success', r))
  .catch(e => console.error('error', e))
export default client
import { createServer } from 'service-mocker/server'

const { router } = createServer()

router.get('/users', (req, res) => {
  res.json([{
    name: 'Brett'
  }])
})

Storage resolution

Although we are able to use asynchronous storage APIs like indexedDB within service worker context, it's considered unreliable because any little update may create a new context thus you are no longer accessible to the old one.

So I decided to use the client-side storage with localForage, a typical workflow may like:

// set
server: storage.set(...) via channel message
client: set item in local storages and report the result via channel message

// get
server: request a storage.get(...) via channel message
client: get item in local storages and send back the result

javascript errors sometimes get swallowed when inspector is running on reload

Issue Summary

In chrome on Mac OS X, when I have the inspector opened and I reload the page and there is an error in my server code, I don't see it however it I have the inspector closed and then reload the page and open the inspector, the error show up in the console log.

Expected Behavior

I should see my server error in the console regardless if the inspector is opened

Current Behavior

In chrome on Mac OS X, when I have the inspector opened and I reload the page and there is an error in my server code, I don't see it however it I have the inspector closed and then reload the page and open the inspector, the error show up in the console log.

Steps to Reproduce

Create a server file with a javascript error in it.

Environment

  • Browser: Chrome
  • Version of service-mocker: 1.0.3

Keep service worker up-to-date

As per W3C Definition, service workers(SW for short) are designed more like stand-by services:

lifecycle

From the above figure, we know that a new version of SW updates only if there's no client controlled. By that means, we can not update SW in time even we reload and reload and reload...

But by calling self.skipWaiting(), we can immediately active a waiting SW - that means the new SW will turn to activated state - then we can use clients.claim() method to takeover of all pages within scope. So here comes the workaround:

event: install => self.skipWaiting()
event: activated => clients.claim()

Serious bug with route matching with nested routes

If we look at the code that handles the logic for matching routes:

    // strip router's base path
    const re = new RegExp(`^${this._basePath}`);
    const path = url.pathname.replace(re, '');

    for (let rule of this._rules) {
      const {
        method,
        regex,
        callback,
      } = rule;

      if (regex.test(path) && (request.method === method || rule.isAll)) {

The following 2 lines:

    const re = new RegExp(`^${this._basePath}`);
    const path = url.pathname.replace(re, '');

Will strip the router's bash path, and then try to match the reminder with:

  if (regex.test(path) && (request.method === method || rule.isAll)) {

This is incorrect logic, frankly I don't understand how this logic was able to exist without an issue opened, unless people use a very simple routing setup.

This line of code const path = url.pathname.replace(re, ''); works wether the pathname contain, or does not contain the basePath

So if my base path is /api/customers and the request is / nothing it removed which is ok but the router goes on as if it's ok, and it is not.

To illustrate:

  • The service mocker is configured with the base path /api
  • A scoped router is created with the path /customers (which makes it's base path /api/customers
  • A route is added to the scoped router / which is the root.

Now when we try to match a request with GET and pathname /
the router will match the last route we configured, which is:

  • basePath: /api/customers
  • route: /

This is incorrect and it happen because there is no check when doing the replace, the replace should actually be something else:

    const re = new RegExp(`^${this._basePath}(.*)`);
    const match = re.exec(url.pathname);

  if (match && regex.test(match[1]) && (request.method === method || rule.isAll)) {

Integration with CRA?

Hi guys,
This project is amazing!
Would you be so kind as to demonstrate how to integrate it with create-react-app application?
I've tried ejecting, adding additional entry point (so it would split server.js from bundle), but it only works in legacy mode.

Any tips would be highly appreciated!

Thanks in advance

Problems with basinc routing

Environment

  • Browser: All
  • Version of service-mocker: 2.0.0

Current Behavior

Basic routing doesn't work.

Steps to Reproduce

Webpack 2.2.0
sw-loader:

  • import scriptURL from 'sw-loader!./assets/server-mock/server.bundle.js';
  • const client = createClient(scriptURL);

server.bundle.js:

router.put('/groups/:id', (req, res) => {
  return res.json({});
});

Trying a similar link gives 404: 'localhost/groups/1'

  • Uncaught TypeError: Cannot read property '0' of undefined

I've debugged a little bit, and the error occurs in service-mocker/server/request.js:

// skip full matched string at [0]
 var max = matches.length;
 for (var i = 1; i < max; i++) {
   var name = keys[i - 1].name;                  -----> keys is undefined.

   params[name] = decodeParam(matches[i]);
 }

Browserify example

Tried to run the readme example with simple browserify setup:

// server.js
import { createServer } from 'service-mocker/server';

const { router } = createServer();

// or you can use the shorthand method
router.get('/greet', 'Hello new world!');
  // client.js

  require('service-mocker-polyfills')
  var { createClient } = require('service-mocker/client')

  const client = createClient('server.js')

  await client.ready
  response = await fetch('/ok')
  console.log(await response.text())

I get Uncaught SyntaxError: Unexpected token { at server.js:1, which is unrecognized import statement.

require('service-mocker/server.js') is not available since require is not available.
Would be nice to have some browserify transform to do that easily, similar to workerify

Get to know when the service worker is ready for using

Service worker(SW for short) provides a ready property to indicate whether a SW is ready to control current page. However, with an activated SW, the ready promise will be immediately resolved to the active SW in spite of any updates(spec). So if we perform the update algorithm in #2, we are likely to get the old SW controller.

By digging into the W3C Draft, I found something interesting in the update algorithm section:

#Update
10. Invoke Install algorithm with job, worker, and registration as its arguments.

#Install
4. Run the Update Registration State algorithm passing registration, "installing" and worker as the arguments.
...
7. Invoke Resolve Job Promise with job and the ServiceWorkerRegistration object which represents registration.

The above algorithm implies that when navigator.serviceWorker.ready resolved, registration.installing will be set to the new SW! So the resolution is:

if serviceWorker.controller is null
    return serviceWorker.ready
else
    request an update as:

    await registration.update()

    if registration.installing is not null
       set new worker to the installing worker
       wait until the new worker is activated

Now the ready will always be resolved with the newest registration ๐Ÿ‘

Project Apollo - the blueprint for the first version

Hey guys, welcome to the future! This is the outline of service mocker.

0x00 - Explaination

API mock service sucks

Generally, there are two ways to mock an API for your front-end projects:

  1. Set up a temporary server to provide the mock data.
  2. Write mock data directly in your codes.

For the first one, you may have to do tons of works just to set up a simple server. And for the second one, you have to pollute your codes with fake data. What if there's a magic that allows you to mock in front-end and act just like a real server?

Today, with the service worker API, we can find out a way to intercept requests and respond with real HTTP responses that are generated in the service worker context. That's the main idea of this project.

Advantages

  1. Easy to set up.
  2. Real requests that can be inspected in dev tools.
  3. No proxies, no separate server addresses, no CORS issues, no pains.

Disadvantages

  1. Poor compatibility: http://caniuse.com/#feat=serviceworkers
  2. Weird behaviors among old version of browsers.

0x01 - Infrastructure

In an ideal world, only scripts for service workers are needed for running a requests interceptor. But as there are so many limitations with service worker, we are likely to wrap a client runtime for it.

  • Client
    • storage
    • service worker updater
  • Server
    • router
    • request and response layers
    • storage service

0x02 - Features List

  • Client

    • real-time sw scripts updater, #2
    • legacy browers support, #5
  • Server

    • express-style router module
    • request layer
    • response layer

CC @vincentbel

Chainable baseURL

Currently(v1.0.x), router.base() will always resolve to the origin of current page. For example:

const { router } = createServer();

router.base('/api'); // => http://localhost:3000/api

However, this will be counterintuitive when we code as:

const { router } = createServer('/api');

router.base('/v1'); // => http://localhost:3000/v1

Instead of getting a path of /api/v1, we got /v1 :(. So I'm wondering if we should resolve the given baseURL against current baseURL, just like the mount-paths in express apps.

CC @vincentbel

Unhandled Promise rejection: Unable to get property 'length' of undefined or null reference ;

Environment

  • Browser: IE11
  • Version of service-mocker: 2.1.1

Hi guys. There seams to be little bug in the code below:

self.addEventListener('message', function () {
  var _ref = _asyncToGenerator(_regenerator2.default.mark(function _callee(event) {
    var data, ports, port;
    return _regenerator2.default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            data = event.data, ports = event.ports;
            if (!(!data || !ports.length)) {
              _context.next = 3;
              break;
            }
          ...

Namely, ports is not always defined, hence the error in the title.

Wildcard URL param doesn't treat well empty param

Environment

  • Browser: Chrome 59
  • Version of service-mocker: 2.0.0

Expected Behavior

Wildcard URL parameters should give an undefined content when empty

Current Behavior

They are instead "undefined" (a string containing the word "undefined")

Steps to Reproduce

// sw.js
import { createServer } from 'service-mocker/server'
const { router } = createServer()
router.get(`/asd/:path*`, (req, res) => {
  const { path } = req.params
  if (path === 'undefined') {
    console.warn(`Path is the string "undefined"!`)
  } else {
    console.log(`Path is "${path}"`)
  }
})

// index.js
window.fetch(`/asd/foo/bar`) // => Log: Path is "foo/bar"
window.fetch(`/asd/`) // => Warning: Path is the string "undefined"!

Add middlewares into the server

It could be nice to have a simple middleware mechanism into the MockerServer (like express). It could allow to add some generic logic around each route: simulate latency, add header to the response...

What did you think about it?

Support a catch all handler

From a brief inspection of the code it looks like the order setting the routes does not ensure the order of execution.

For example, setting route /A and then setting a scope and on it setting route /B and after that setting route /* on the first router will never hit on /B.

This is how the matching logic works...

Which makes it difficult to create a catch all route.

To do that the workaround is to create a scope with / after all routes have been creates, which will ensure it matching last.

The catch all will hit when nothing was found, but the url is on the base URL specified. (e.g. /api)
Is it possible to make it part of the api?

Project Phoenix - roadmap of ver. 2

Hey guys, we got some problems.

0x00 - Issues with TypeScript

โ… . The default libs in TypeScript are not perfect

Neither ts2.2 nor ts2.3 provides complete typings for service workers. What's more, they mixed together service worker context and worker context, which brings some other issues to us :/

II. Compilations across multiple contexts are difficult

service-mocker contains two parts: client for DOM side and server for service worker side. However, lib.dom and lib.worker are mutually exclusive. So we have to create multiple separate compilations, which cause unnecessary complexity.

0x01 - Attempts

Multiple compilations

As argued above, multiple compilations bring unnecessary complexity to us. @vincentbel has been working on support-ts2.2 branch for long but still can not get a perfect workaround. The duplicate compilations of shared parts, i.e. constants/ and utils/, seem to be unavoidable.

Besides, the lack of typings also causes headaches. And it's considered hard to maintenance those typings with the monthly release of TypeScript.

Switch to Babel + Flow

As an alternation of TypeScript, Flow is still immature now. We couldn't even find a proper plugin or tool for our editors. Life is short, don't waste your time fighting against tools ๐Ÿ™ƒ .

0x02 - v2, and future

So as a result, we'd like to rewrite this project with Babel. We may switch back to TypeScript or Flow some day, but first of all, our goal is to solve the problems in daily development ;)

0x03 - TODO

  • Refactor with babel. (#30)
  • Standalone typings file. (#33)
  • Publish a patch release for TypeScript compatibility check in v1.x.
  • Release v2.0.0.

CC @vincentbel

ngc+AOT+webpack - compile error

Issue Summary

The following error occurs, when I try to build my project using webpack and taking advantage of AOT

Error: Error encountered resolving symbol values statically. Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler (position 3:22 in the original .ts file), resolving symbol ExtandableRequest in D:/01_trunk/angular2-webpack-starter/node_modules/service-mocker/server/request.d.ts, resolving symbol MockerRequest in D:/01_trunk/angular2-webpack-starter/node_modules/service-mocker/server/request.d.ts

I have a feeling that it is related to this: https://medium.com/@isaacplmann/making-your-angular-2-library-statically-analyzable-for-aot-e1c6f3ebedd5#.1ubrczudd ...

Tsc works just fine.

Expected Behavior

Succesful compilation.

Environment

  • Version of service-mocker: 1.1.0

Remove hash tags (#) from URL

Environment

  • Browser: Chrome
  • Version of service-mocker: 2.1.2

Expected Behavior

The hash of a URL should not be part of the url when processing.

Current Behavior

Hash is a part of the url when processing.

When browsers send an HTTP request they omit the hash part of the URL.
It appears that the fetch event sends the Request object with the url as is, no modifications.

This makes sense because a service worker is in the browser, it might want to know that.

However, the service mocker mocks a server which does not get this information, more specifically the router does not know how to handle that.

So localhost:3000/#page1 will pass for this rule /\/.+/ altough it does not suppose to... this is because the concept of hash does not exist in the server...

Legacy browsers workaround

Service worker API is such a new API that it can only run in most recent browsers. Therefore supporting legacy browsers becomes a big issue for us.

The most important part in worker scripts - HTTP requests interception - is accomplished with fetch event, so if we can emulate fetch events, we can make the worker scripts work in client context by listening to window.addEventListener('fetch', handler).

Creating a custom event is easy:

const fetchEvt = new Event('fetch', {
  bubbles: true,
});

self.dispatchEvent(fetchEvt);

The problem is, how to respond to this event? Unlike native fetch events, we have no way to know whether the event is handled. Anyway the first thing to do is to mock an event.respondWith() interface:

fetchEvt.respondWith = (response) => {
  resolve(response);
};

As for the handled-or-not issue, okay, let's do something rude:

setTimeout(() => {
  resolve(fetch(request))
}, 300);

Hey, look! You have 300ms to invoke event.respondWith() method, after time runs out, I'll do a real fetch myself to the thing I want. That's so fair, right ๐Ÿ™ƒ

Is this project still active?

Hi, thanks for creating this awesome project.

I just made some non-backward compatible changes to this project and found out the last commit from authors was over 2 years ago.

I'm wondering that Should I send a PR or is ok that I fork to another project(rename and keep credit)?

any suggestion?

Don't put typings as dependencies

Issue Summary

Right now it's impossible to use Typescript@next and this library, when using "dom" in the "lib" of tsconfig.json, because it's user defined typings of typings already defined in lib.dom.d.ts, like Fetch and Streams.

Expected Behavior

Put the whatg and service workers in peerDependencies, so at least the compile time won't choke

Current Behavior

Project won't compile because of conflicting redefinitions from whatg and service_worker_api typings and lib.dom.d.ts 'stdlib'

Steps to Reproduce

  1. Install typescript@next
  2. Set tsconfig.json "compilerOptions.lib" to ["dom"]
  3. Install service-mocker
  4. Try to compile project

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.