├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md ├── tslint.json └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Outputs 2 | *.js 3 | *.js.map 4 | *.d.ts 5 | 6 | # IDEs 7 | .idea/ 8 | jsconfig.json 9 | .vscode/ 10 | 11 | # Misc 12 | node_modules/ 13 | npm-debug.log* 14 | yarn-error.log* 15 | 16 | # Mac OSX Finder files. 17 | **/.DS_Store 18 | .DS_Store -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "tsconfig", 4 | "lib": [ 5 | "es6", 6 | "dom" 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "declaration": true, 13 | "noEmitHelpers": false, 14 | "noEmitOnError": false, 15 | "rootDir": "src/", 16 | "skipDefaultLibCheck": true, 17 | "skipLibCheck": true, 18 | "noImplicitAny": false, 19 | "noUnusedLocals": true, 20 | "strict": true, 21 | "sourceMap": true, 22 | "target": "es5" 23 | } 24 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-mock", 3 | "version": "2.0.1", 4 | "description": "Mock utilities for Angular", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "tsc" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://amcdnl@github.com/amcdnl/ngx-mock.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "mock", 16 | "testing" 17 | ], 18 | "author": "Austin McDaniel", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/amcdnl/ngx-mock/issues" 22 | }, 23 | "homepage": "https://github.com/amcdnl/ngx-mock#readme", 24 | "dependencies": { 25 | "path-to-regexp": "^2.2.1" 26 | }, 27 | "peerDependencies": { 28 | "angular-in-memory-web-api": "^0.6.0" 29 | }, 30 | "devDependencies": { 31 | "angular-in-memory-web-api": "^0.6.0", 32 | "typescript": "^2.7.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2018 Austin McDaniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx-mock 2 | A mocking utility library that builds on [Angular In Memory Web Api](https://github.com/angular/in-memory-web-api) 3 | library. 4 | 5 | ## Usage 6 | The library allows you to decorate methods in the `InMemoryDbService` implementation 7 | using the URL path so you can easily map mock endpoints to mock data. 8 | 9 | ```javascript 10 | import { matchRoute, MockPost } from 'ngx-mock'; 11 | 12 | export class InMemoryDb implements InMemoryDbService { 13 | createDb(reqInfo?: RequestInfo) { 14 | return []; 15 | } 16 | 17 | get(reqInfo: RequestInfo) { 18 | return matchRoute(this)('get', reqInfo); 19 | } 20 | 21 | put(reqInfo: RequestInfo) { 22 | return matchRoute(this)('put', reqInfo); 23 | } 24 | 25 | post(reqInfo: RequestInfo) { 26 | return matchRoute(this)('post', reqInfo); 27 | } 28 | 29 | delete(reqInfo: RequestInfo) { 30 | return matchRoute(this)('delete', reqInfo); 31 | } 32 | 33 | @MockPost('api/requests/approve') 34 | postRequestApprove(reqInfo) { 35 | const index = request.findIndex(r => r.requestId === reqInfo.req.body.requestId); 36 | const request = requests[index]; 37 | request.state.approved = true; 38 | return { body: request }; 39 | } 40 | } 41 | ``` -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "callable-types": true, 4 | "class-name": true, 5 | "comment-format": [ 6 | true, 7 | "check-space" 8 | ], 9 | "curly": true, 10 | "eofline": true, 11 | "import-blacklist": [ 12 | true, 13 | "rxjs" 14 | ], 15 | "import-spacing": true, 16 | "indent": [ 17 | true, 18 | "spaces" 19 | ], 20 | "label-position": true, 21 | "max-line-length": [ 22 | true, 23 | 140 24 | ], 25 | "member-access": false, 26 | "member-ordering": [ 27 | true, 28 | { 29 | "order": [ 30 | "static-field", 31 | "instance-field", 32 | "static-method", 33 | "instance-method" 34 | ] 35 | } 36 | ], 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-console": [ 40 | true, 41 | "debug", 42 | "info", 43 | "time", 44 | "timeEnd", 45 | "trace" 46 | ], 47 | "no-construct": true, 48 | "no-debugger": true, 49 | "no-duplicate-super": true, 50 | "no-empty": false, 51 | "no-eval": true, 52 | "no-inferrable-types": [ 53 | true, 54 | "ignore-params" 55 | ], 56 | "no-misused-new": true, 57 | "no-non-null-assertion": true, 58 | "no-shadowed-variable": true, 59 | "no-string-literal": false, 60 | "no-string-throw": true, 61 | "no-switch-case-fall-through": true, 62 | "no-trailing-whitespace": true, 63 | "no-unnecessary-initializer": true, 64 | "no-use-before-declare": false, 65 | "no-var-keyword": true, 66 | "object-literal-sort-keys": false, 67 | "one-line": [ 68 | true, 69 | "check-open-brace", 70 | "check-catch", 71 | "check-else", 72 | "check-whitespace" 73 | ], 74 | "prefer-const": true, 75 | "quotemark": [ 76 | true, 77 | "single" 78 | ], 79 | "radix": true, 80 | "semicolon": [ 81 | true, 82 | "always" 83 | ], 84 | "triple-equals": [ 85 | true, 86 | "allow-null-check" 87 | ], 88 | "typedef-whitespace": [ 89 | true, 90 | { 91 | "call-signature": "nospace", 92 | "index-signature": "nospace", 93 | "parameter": "nospace", 94 | "property-declaration": "nospace", 95 | "variable-declaration": "nospace" 96 | } 97 | ], 98 | "typeof-compare": true, 99 | "unified-signatures": true, 100 | "variable-name": false, 101 | "whitespace": [ 102 | true, 103 | "check-branch", 104 | "check-decl", 105 | "check-operator", 106 | "check-separator", 107 | "check-type" 108 | ] 109 | } 110 | } -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import * as pathToRegexp from 'path-to-regexp'; 2 | import { 3 | STATUS, 4 | getStatusText, 5 | RequestInfo 6 | } from 'angular-in-memory-web-api'; 7 | 8 | const MOCK_META = '__mock__'; 9 | 10 | /** 11 | * Utility for matching mock URL decorators to the corresponding method 12 | * and creating a HTTP response. 13 | * 14 | * Invoke it like this: 15 | * matchRoute(myInMemoryDbInstance)(typeOfReq, reqInfo); 16 | */ 17 | export function matchRoute(ctrl) { 18 | const routes = ctrl[MOCK_META]; 19 | 20 | return (type, req) => { 21 | console.log(`[HTTP] Request override`, type, req.url); 22 | 23 | for (const { 24 | method, 25 | path, 26 | name 27 | } of routes) { 28 | if (path) { 29 | const keys = []; 30 | const re = pathToRegexp(path, keys); 31 | 32 | // strip query params 33 | const [url] = req.url.split('?'); 34 | const match = re.test(url); 35 | 36 | if (match) { 37 | if (method === type) { 38 | // attach our mapped params to the reqinfo 39 | req.route = mapParams(re, keys, req.url); 40 | // invoke our method wrapping the response 41 | return req.utils.createResponse$(() => finishOptions(ctrl[name](req), req)); 42 | } 43 | } 44 | } 45 | } 46 | 47 | // if we didn't match a URL, call the generic type 48 | for (const { 49 | method, 50 | path, 51 | name 52 | } of routes) { 53 | if (path === undefined && type === method) { 54 | return req.utils.createResponse$(() => finishOptions(ctrl[name](req), req)); 55 | } 56 | } 57 | 58 | throw new Error(`Route not matched ${type}:${req.url}`); 59 | }; 60 | } 61 | 62 | /** 63 | * Given a regex-path-map expression and a url, it returns 64 | * the parameters for the given url. 65 | * 66 | * Example: 67 | * /api/foo/:bar/:car 68 | * 69 | * with the following url invoked: 70 | * api/foo/100/porche 71 | * 72 | * would return: 73 | * { bar: '100', car: 'porche' } 74 | * 75 | * Adapted from: https://github.com/pillarjs/path-match 76 | */ 77 | function mapParams(re, keys, pathname, params = {}) { 78 | const m = re.exec(pathname); 79 | if (!m) { 80 | return false; 81 | } 82 | 83 | let key; 84 | let param; 85 | for (let i = 0; i < keys.length; i++) { 86 | key = keys[i]; 87 | param = m[i + 1]; 88 | 89 | if (!param) { 90 | continue; 91 | } 92 | 93 | params[key.name] = decodeURIComponent(param); 94 | 95 | if (key.repeat) { 96 | params[key.name] = params[key.name].split(key.delimiter); 97 | } 98 | } 99 | 100 | return params; 101 | } 102 | 103 | /** 104 | * Method to polish out the http req info. 105 | */ 106 | export function finishOptions(options, { 107 | headers, 108 | url 109 | }: RequestInfo) { 110 | options.status = options.status || STATUS.OK; 111 | options.statusText = getStatusText(options.status); 112 | options.headers = headers; 113 | options.url = url; 114 | return options; 115 | } 116 | 117 | /** 118 | * Creates metadata object. 119 | * https://github.com/angular/angular/blob/master/packages/core/src/util/decorators.ts#L60 120 | */ 121 | export function ensureStoreMetadata(target: any) { 122 | if (!target.hasOwnProperty(MOCK_META)) { 123 | Object.defineProperty(target, MOCK_META, { 124 | value: [] 125 | }); 126 | } 127 | return target[MOCK_META]; 128 | } 129 | 130 | /** 131 | * Decorator for defining a mock method. 132 | * 133 | * Example: 134 | * @Mock('get', 'api/foo/:bar') 135 | * myFn(reqInfo) { ... } 136 | */ 137 | export function Mock(method: string, path ? : string) { 138 | return (target: any, name: string, descriptor: TypedPropertyDescriptor < any > ) => { 139 | const metadata = ensureStoreMetadata(target); 140 | metadata.push({ 141 | method, 142 | path, 143 | name 144 | }); 145 | }; 146 | } 147 | 148 | /** 149 | * Decorator shortcut for calling `Mock` decorator with 'get' parameter. 150 | */ 151 | export const MockGet = (path ? : string) => Mock('get', path); 152 | 153 | /** 154 | * Decorator shortcut for calling `Mock` decorator with 'post' parameter. 155 | */ 156 | export const MockPost = (path ? : string) => Mock('post', path); 157 | 158 | /** 159 | * Decorator shortcut for calling `Mock` decorator with 'put' parameter. 160 | */ 161 | export const MockPut = (path ? : string) => Mock('put', path); 162 | 163 | /** 164 | * Decorator shortcut for calling `Mock` decorator with 'delete' parameter. 165 | */ 166 | export const MockDelete = (path ? : string) => Mock('delete', path); --------------------------------------------------------------------------------