Skip to content

Instantly share code, notes, and snippets.

@amcdnl
Created January 15, 2018 17:14
Show Gist options
  • Save amcdnl/1851af86e4d677746389b169db090cc9 to your computer and use it in GitHub Desktop.
Save amcdnl/1851af86e4d677746389b169db090cc9 to your computer and use it in GitHub Desktop.

Angular In-Memory-Db Mocker

A nifty decorator utility to map in memory db methods to http paths. It allows you to decorate methods with a API route and map a mock operation to it. So you simple do:

@MockPost('api/requests/approve/:id')
postRequestApprove(reqInfo) {
  ...
}

and when Angular makes a HTTP call, it will invoke this method instead of calling the backend and allow you to mock it.

Extended Example

Below is a extended example showing how to use with InMemoryDbService.

export class InMemoryDb implements InMemoryDbService {
    createDb(reqInfo?: RequestInfo) {
        return [];
    }

    get(reqInfo: RequestInfo) {
        return matchRoute(this)('get', reqInfo);
    }

    put(reqInfo: RequestInfo) {
        return matchRoute(this)('put', reqInfo);
    }

    post(reqInfo: RequestInfo) {
        return matchRoute(this)('post', reqInfo);
    }

    delete(reqInfo: RequestInfo) {
        return matchRoute(this)('delete', reqInfo);
    }
    
    @MockPost('api/requests/approve')
    postRequestApprove(reqInfo) {
        const index = request.findIndex(r => r.requestId === reqInfo.req.body.requestId);
        const request = requests[index];
        request.state.approved = true;
        return { body: request };
    }
    
}
import * as pathToRegexp from 'path-to-regexp';
import { STATUS, getStatusText, RequestInfo } from 'angular-in-memory-web-api';
/**
* Utility for matching mock URL decorators to the corresponding method
* and creating a HTTP response.
*
* Invoke it like this:
* matchRoute(myInMemoryDbInstance)(typeOfReq, reqInfo);
*/
export function matchRoute(ctrl) {
const routes = Reflect.getMetadata('$MOCK', ctrl);
return (type, req) => {
console.log(`HTTP ${type} override`, req.url);
for (const { method, path, name } of routes) {
if (path) {
const keys = [];
const re = pathToRegexp(path, keys);
const match = re.test(req.url);
if (match) {
if (method === type) {
// attach our mapped params to the reqinfo
req.route = mapParams(re, keys, req.url);
// invoke our method wrapping the response
return req.utils.createResponse$(() => finishOptions(ctrl[name](req), req));
}
}
}
}
// if we didn't match a URL, call the generic type
for (const { method, path, name } of routes) {
if (path === undefined && type === method) {
return req.utils.createResponse$(() => finishOptions(ctrl[name](req), req));
}
}
throw new Error(`Route not matched ${type}:${req.url}`);
};
}
/**
* Given a regex-path-map expression and a url, it returns
* the parameters for the given url.
*
* Example:
* /api/foo/:bar/:car
*
* with the following url invoked:
* api/foo/100/porche
*
* would return:
* { bar: '100', car: 'porche' }
*
* Adapted from: https://github.com/pillarjs/path-match
*/
function mapParams(re, keys, pathname, params = {}) {
const m = re.exec(pathname);
if (!m) {
return false;
}
let key;
let param;
for (let i = 0; i < keys.length; i++) {
key = keys[i];
param = m[i + 1];
if (!param) {
continue;
}
params[key.name] = decodeURIComponent(param);
if (key.repeat) {
params[key.name] = params[key.name].split(key.delimiter);
}
}
return params;
}
/**
* Method to polish out the http req info.
*/
export function finishOptions(options, { headers, url }: RequestInfo) {
options.status = options.status || STATUS.OK;
options.statusText = getStatusText(options.status);
options.headers = headers;
options.url = url;
return options;
}
/**
* Decorator for defining a mock method.
*
* Example:
* @Mock('get', 'api/foo/:bar')
* myFn(reqInfo) { ... }
*/
export function Mock(method: string, path?: string) {
return (target: any, name: string, descriptor: TypedPropertyDescriptor<any>) => {
const meta = Reflect.getMetadata('$MOCK', target) || [];
meta.push({ method, path, name });
Reflect.defineMetadata('$MOCK', meta, target);
};
}
/**
* Decorator shortcut for calling `Mock` decorator with 'get' parameter.
*/
export const MockGet = (path?: string) => Mock('get', path);
/**
* Decorator shortcut for calling `Mock` decorator with 'post' parameter.
*/
export const MockPost = (path?: string) => Mock('post', path);
/**
* Decorator shortcut for calling `Mock` decorator with 'put' parameter.
*/
export const MockPut = (path?: string) => Mock('put', path);
/**
* Decorator shortcut for calling `Mock` decorator with 'delete' parameter.
*/
export const MockDelete = (path?: string) => Mock('delete', path);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment