├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── example ├── app │ └── app.js ├── index.html ├── mocks │ ├── default.js │ ├── format.js │ ├── multiple.js │ └── users.js ├── protractor-conf.js └── spec │ ├── casing.spec.js │ ├── convenience.spec.js │ ├── default.spec.js │ ├── external.spec.js │ ├── fromFile.spec.js │ ├── get.js │ ├── headers.spec.js │ ├── httpDefaults.spec.js │ ├── inline.spec.js │ ├── jsonFormat.spec.js │ ├── params.spec.js │ ├── plugin.spec.js │ ├── postData.spec.js │ ├── queryString.spec.js │ ├── regex.spec.js │ ├── requestsMade.spec.js │ └── runtime.spec.js ├── index.js ├── lib ├── defaultConfig.js ├── httpMock.js └── initData.js ├── package.json ├── specs ├── httpMock.spec.js └── initData.spec.js └── tests ├── convenienceMethods.test.js ├── directCalls.test.js ├── externalUrl.test.js ├── headers.test.js ├── interceptors.test.js ├── plugin.test.js ├── promises.test.js ├── queryString.test.js ├── regex.test.js ├── requests.test.js ├── runtime.test.js ├── setup.js ├── template.tmpl └── transforms.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.sublime* 3 | *.log 4 | .DS_Store 5 | .grunt 6 | _SpecRunner.html 7 | tests/bundle/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "loopfunc": true, 22 | "globals": { 23 | "angular": false, 24 | "protractor": false, 25 | "define": false, 26 | "require": false, 27 | "describe": false, 28 | "it": false, 29 | "beforeEach": false, 30 | "afterEach": false, 31 | "expect": false, 32 | "inject": false, 33 | "jasmine": false, 34 | "spyOn": false, 35 | "xit": false, 36 | "xdescribe": false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | bundler_args: --retry 3 3 | language: node_js 4 | node_js: 5 | - 4.2.2 6 | cache: 7 | directories: 8 | - node_modules/ 9 | install: 10 | - npm set progress=false 11 | - travis_retry npm install 12 | before_script: 13 | - npm install -g grunt-cli 14 | script: 15 | - npm run webdriver-update 16 | - npm test 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to protractor-http-mock 2 | This project has come a long way thanks to the community, and its growth has been very much a result from countless suggestions and pull requests. Thank you all! 3 | 4 | ## Pull Requests 5 | I strongly encourage developers to submit pull requests for new features and bug fixes. Please add unit tests and/or an examples to any pull request submitted. This goes a long way in getting the pull request merged in as soon as possible. 6 | 7 | This project is developed using grunt. To run all tests, simply execute 8 | 9 | `npm install` 10 | `npm run webdriver-update` 11 | `npm test` 12 | 13 | * To add a unit test, please refer to tests/httpMock.test.js 14 | * To add an example, please refer to the example/ folder. 15 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | jshint: { 6 | options: { 7 | jshintrc: '.jshintrc', 8 | }, 9 | files: ['lib/**/*.js'] 10 | }, 11 | jasmine_nodejs: { 12 | all: { 13 | specs: ['specs/*.js'] 14 | } 15 | }, 16 | connect: { 17 | example: { 18 | options: { 19 | base: 'example' 20 | } 21 | }, 22 | 'client-test': { 23 | options: { 24 | base: '.' 25 | } 26 | } 27 | }, 28 | protractor: { 29 | options: { 30 | configFile: 'example/protractor-conf.js', 31 | //debug: true 32 | }, 33 | example: {} 34 | }, 35 | browserify: { 36 | test: { 37 | files: { 38 | 'tests/bundle/httpMock.js': ['lib/httpMock.js'] 39 | }, 40 | options: { 41 | browserifyOptions: { 42 | standalone: 'httpMock' 43 | } 44 | } 45 | } 46 | }, 47 | jasmine: { 48 | test: { 49 | src: 'tests/bundle/httpMock.js', 50 | options: { 51 | specs: 'tests/*.test.js', 52 | vendor: [ 53 | 'http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.js' 54 | ], 55 | helpers: [ 56 | 'tests/setup.js' 57 | ], 58 | template: 'tests/template.tmpl' 59 | } 60 | } 61 | } 62 | }); 63 | 64 | grunt.loadNpmTasks('grunt-contrib-jshint'); 65 | grunt.loadNpmTasks('grunt-jasmine-nodejs'); 66 | grunt.loadNpmTasks('grunt-contrib-connect'); 67 | grunt.loadNpmTasks('grunt-protractor-runner'); 68 | grunt.loadNpmTasks('grunt-browserify'); 69 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 70 | 71 | grunt.registerTask('host-example', ['connect:example:keepalive']); 72 | grunt.registerTask('example', ['connect:example', 'protractor:example']); 73 | grunt.registerTask('test', ['jasmine_nodejs']); 74 | grunt.registerTask('lint', ['jshint']); 75 | grunt.registerTask('client-test', ['browserify:test', 'jasmine:test']); 76 | 77 | grunt.registerTask('verify', ['lint', 'test', 'client-test', 'example']); 78 | }; 79 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Carlos Atencio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Protractor Mock 2 | A NodeJS module to be used alongside [Protractor](https://github.com/angular/protractor) to facilitate setting up mocks for HTTP calls for the AngularJS applications under test. 3 | 4 | This allows the developer to isolate the UI and client-side application code in our tests without any dependencies on an API. 5 | 6 | **This plugin does not depend on Angular Mocks (ngMockE2E) being loaded into your app; therefore, there is no need to modify anything within your current Angular web application.** 7 | 8 | [![Build Status](https://travis-ci.org/atecarlos/protractor-http-mock.png)](https://travis-ci.org/atecarlos/protractor-http-mock) 9 | 10 | ## Installation 11 | npm install protractor-http-mock --save-dev 12 | ## Configuration 13 | In your protractor configuration file, we can set the following options: 14 | 15 | ### Mocks 16 | We can set a collection of default mocks to load for every test, and the name of the folder where your mocks will reside. More on this later. 17 | 18 | mocks: { 19 | default: ['mock-login'], // default value: [] 20 | dir: 'my-mocks' // default value: 'mocks' 21 | }, 22 | 23 | ### Directories and file names 24 | We can also configure our root directory where the mocks and protractor configuration will be located; as well as, the name of the protractor configuration file. 25 | 26 | onPrepare: function(){ 27 | require('protractor-http-mock').config = { 28 | rootDirectory: __dirname, // default value: process.cwd() 29 | protractorConfig: 'my-protractor-config.conf' // default value: 'protractor-conf.js' 30 | }; 31 | } 32 | 33 | ## Usage 34 | Mocks are defined as JSON objects describing the request and response for a particular HTTP call: 35 | 36 | { 37 | request: { 38 | path: '/users/1', 39 | method: 'GET' 40 | }, 41 | response: { 42 | data: { 43 | userName: 'pro-mock', 44 | email: 'pro-mock@email.com' 45 | } 46 | } 47 | } 48 | 49 | And then set the mock at the beginning of your test before your application loads: 50 | 51 | var mock = require('protractor-http-mock'); 52 | ... 53 | 54 | mock([{ 55 | request: { 56 | path: '/users/1', 57 | method: 'GET' 58 | }, 59 | response: { 60 | data: { 61 | userName: 'pro-mock', 62 | email: 'pro-mock@email.com' 63 | } 64 | } 65 | }]); 66 | 67 | Make sure to clean up after test execution. This should be typically done in the `afterEach` call to ensure the teardown is executed regardless of what happens in the test execution: 68 | 69 | afterEach(function(){ 70 | mock.teardown(); 71 | }); 72 | 73 | Please note that the `mock()` function needs to be called before the browser opens. If you have different mock data for different tests, please make sure that, either the tests always start in a new browser window, or that its possible to setup all the mocks for each test case before any of tests start running. 74 | 75 | ### Mock files 76 | Mocks can also be loaded from physical files located in the `mocks.dir` directory that we defined in our configuration: 77 | 78 | tests 79 | e2e 80 | protractor.conf.js 81 | mocks 82 | users.js 83 | specs 84 | ... 85 | 86 | 87 | You could simply load your mocks as follows: 88 | 89 | mock(['users']); 90 | 91 | Files are structured as standard node modules. The strings passed are the path of the file relative to the mocks directory - the same as if you would be doing a standard `require()` call. 92 | 93 | module.exports = { ... }; // for a single mock. 94 | 95 | or 96 | 97 | module.exports = [ ... ]; // for multiple mocks. 98 | 99 | 100 | ### Schema 101 | The full GET schema for defining your mocks is as follows: 102 | 103 | request: { 104 | path: '/products/1/items', 105 | method: 'GET', 106 | regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field. 107 | params: { // These match params as they would be passed to the $http service. This is an optional field. 108 | page: 2, 109 | status: 'onsale' 110 | }, 111 | queryString: { // These match query string parameters that are part of the URL as passed in to the $http service. This is an optional field. 112 | param1: 'My first qs parameters', 113 | param2: 'Second parameter' 114 | }, 115 | headers: { //These match headers as the end result of the expression provided to the $http method. 116 | head1: 'val1', 117 | head2: 'val2' 118 | } 119 | }, 120 | response: { 121 | data: {}, // This is the return value for the matched request 122 | status: 500 // The HTTP status code for the mocked response. This is an optional field. 123 | delay: 2 // number of milliseconds to delay sending back the response. 124 | } 125 | 126 | A full mock for a POST call takes the following options: 127 | 128 | request: { 129 | path: '/products/1/items', 130 | method: 'POST', 131 | regex: false, // Boolean to enable Regular Expression matching on path. This is an optional field. 132 | data: { // These match POST data. This is an optional field. 133 | status: 'onsale', 134 | title: 'Blue Jeans', 135 | price: 24.99 136 | } 137 | }, 138 | response: { 139 | data: { // This is the return value for the matched request 140 | status: 'onsale', 141 | title: 'Blue Jeans', 142 | id: 'abc123', 143 | price: 24.99 144 | }, 145 | status: 204 // The HTTP status code for the mocked response. This is an optional field. 146 | } 147 | 148 | PUT, DELETE, HEAD, PATCH, and JSONP methods are also supported. Please see the examples in the source code for more information. 149 | 150 | #### Request 151 | Defining `params`, `queryString`, `headers`, or `data` will help the plugin match more specific responses but neither is required. Both correspond to their matching objects as they would be passed into the $http object call. 152 | 153 | Headers must be defined as the headers that will be used in the http call. Therefore, if in the code to be tested, the headers are defined using properties with function values, these functions will be evaluated as per the $http specification and matched by end result. 154 | 155 | #### Response 156 | The default `status` value is 200 if none is specified. 157 | 158 | An optional `delay` value can be set on the response to assert any state that occurs when waiting for the response in your application, i.e. loading messages or spinners. Please note that UI tests with timing expectations can be somewhat unstable and provide inconsistent results. Please use this feature carefully. 159 | 160 | ### Precendence 161 | protractor-http-mock will respond with the **last** matched request in case there are several matches. The plugin will start matching the default mocks first, followed by those added within the test itself in the order they are added. In other words, the last mock defined will always win. 162 | 163 | ### Inspection 164 | For testing or debugging purposes, it is possible to extract a list of http requests. Simply call the `requestsMade` function as follows: 165 | 166 | var mock = require('protractor-http-mock'); 167 | ... 168 | 169 | expect(mock.requestsMade()).toEqual([ 170 | { url : '/default', method : 'GET' }, 171 | { url : '/users', method : 'GET' } 172 | ]); 173 | 174 | It is also possible to clear the list of requests with the `clearRequests()` method. 175 | 176 | If you wish to assert anything but the full list of requests, then you can do the following to piece out the information needed on the requests: 177 | 178 | mock.requestsMade().then(function(requests){ 179 | expect(requests[1]).toEqual({ url : '/users', method : 'GET' }) 180 | }); 181 | 182 | ### Runtime mocks 183 | If there is a need to add or remove mocks during test execution, please use the `add()` and `remove()` functions: 184 | 185 | mock.add([{ 186 | request: { 187 | path: '/users', 188 | method: 'GET', 189 | params: { 190 | name: 'Charlie' 191 | } 192 | }, 193 | response: { 194 | data: { 195 | name: 'Override' 196 | } 197 | } 198 | }]); 199 | 200 | ... 201 | 202 | mock.remove([{ 203 | request: { 204 | path: '/users', 205 | method: 'GET', 206 | params: { 207 | name: 'Charlie' 208 | } 209 | }, 210 | response: { 211 | data: { 212 | name: 'Override' 213 | } 214 | } 215 | }]); 216 | 217 | These will dynamically modify your current set of mocks, and any new request that happens after that will work with the updated set of mocks. Please note that these functions only work by adding or removing mocks using inline objects. As of now, it is not possible to add or remove mocks using mock files. 218 | 219 | ### Plugins 220 | 221 | Plugins can be used to extend the matching functionality of protractor-http-mock. These are separate from protractor plugins. 222 | 223 | A plugin can be defined as either an NPM package or a function. 224 | 225 | They can be declared in your protractor configuration to be consumed by all your tests: 226 | 227 | baseUrl: 'http://localhost:8000/', 228 | specs: [ 229 | 'spec/*.spec.js' 230 | ], 231 | httpMockPlugins: { 232 | default: ['protractor-http-mock-sample-plugin'] 233 | } 234 | 235 | or in each individual test: 236 | 237 | mock([ 238 | //mocks go here 239 | ], [ 240 | { 241 | match: function(mockRequest, requestConfig){ 242 | ... 243 | } 244 | } 245 | ]); 246 | 247 | Note that in both your protractor configuration and tests, a plugin can be declared as either an npm package name, or definining the object inline. 248 | 249 | See this [sample plugin](https://github.com/atecarlos/protractor-http-mock-sample-plugin) for more information. 250 | 251 | ### Defaults 252 | 253 | If necessary, default mocks and plugins can be skipped for a particular test simply by passing true at the end of your `mock` call: 254 | 255 | mock(mocks, plugins, true); 256 | 257 | 258 | ### Examples 259 | Included in the code base is an extensive list examples on how to use all the features of this plugin. Please take a look if you have any questions. 260 | 261 | To run these tests locally, please follow these steps from the root directory: 262 | 263 | 1. `npm install` 264 | 2. `npm run webdriver-update` 265 | 3. `npm run example` 266 | -------------------------------------------------------------------------------- /example/app/app.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('example', []) 3 | .factory('defaultService', function($http){ 4 | return { 5 | get: function(){ 6 | return $http({ method: 'GET', url: '/default' }); 7 | } 8 | }; 9 | }) 10 | .factory('userService', function($http){ 11 | return { 12 | get: function(){ 13 | return $http({ method: 'GET', url: '/users' }); 14 | }, 15 | post: function(data){ 16 | return $http({ method: 'POST', url: '/users/new', data: data }); 17 | }, 18 | getBy: function(name, city){ 19 | return $http({ 20 | method: 'GET', 21 | url: '/users', 22 | params: { 23 | name: name, 24 | city: city 25 | } 26 | }); 27 | }, 28 | getById: function(id){ 29 | console.log(id); 30 | return $http({ 31 | method: 'GET', 32 | url: '/users/' + id 33 | }); 34 | }, 35 | getByQuery: function(name, city){ 36 | return $http({ 37 | method: 'GET', 38 | url: '/users?name=' + encodeURIComponent(name) + '&city=' + encodeURIComponent(city) 39 | }); 40 | }, 41 | getAuthenticated: function(auth){ 42 | return $http({ 43 | method: 'GET', 44 | url: '/users', 45 | headers: { 46 | auth: auth 47 | } 48 | }); 49 | }, 50 | getFromExternalBy: function(name, city){ 51 | return $http({ 52 | method: 'GET', 53 | url: 'http://some-other.com/users', 54 | params: { 55 | name: name, 56 | city: city 57 | } 58 | }); 59 | }, 60 | getThroughPlugin: function(){ 61 | return $http.get('/users', { 62 | plugin: {} 63 | }); 64 | } 65 | }; 66 | }) 67 | .factory('groupService', function($http){ 68 | return { 69 | get: function(){ 70 | return $http({ method: 'GET', url: '/groups' }); 71 | } 72 | }; 73 | }) 74 | .factory('convenienceService', function($http){ 75 | return { 76 | get: function(){ 77 | return $http.get('/convs') 78 | }, 79 | post: function(data){ 80 | return $http.post('/convs/new', data) 81 | }, 82 | delete: function(id){ 83 | return $http.delete('/convs/' + id) 84 | }, 85 | head: function(){ 86 | return $http.head('/convs'); 87 | }, 88 | jsonp: function(){ 89 | return $http.jsonp('/convs'); 90 | }, 91 | patch: function(id, data){ 92 | return $http.patch('/convs/' + id, data); 93 | }, 94 | put: function(id, data){ 95 | return $http.put('/convs/' + id, data); 96 | } 97 | } 98 | }) 99 | .controller('DefaultController', function(defaultService){ 100 | var self = this; 101 | self.name = ''; 102 | 103 | defaultService.get() 104 | .then(function(response){ 105 | self.name = response.data.name; 106 | }); 107 | }) 108 | .controller('UserController', function(userService){ 109 | var self = this; 110 | 111 | self.users = []; 112 | self.error = null; 113 | self.success = null; 114 | self.showError = false; 115 | self.foundUser = null; 116 | self.authenticated = false; 117 | 118 | userService.get() 119 | .then(function(response){ 120 | self.users = response.data; 121 | }) 122 | .catch(function(response){ 123 | self.showError = true; 124 | self.error = response.data.error; 125 | }); 126 | 127 | function thenHandler(){ 128 | self.success = 'new user saved: ' + self.newUser; 129 | self.newUser = ''; 130 | 131 | self.showError = false; 132 | self.error = null; 133 | } 134 | 135 | function catchHandler(response){ 136 | self.showError = true; 137 | self.error = response.data.error; 138 | 139 | self.success = null; 140 | self.newUser = null; 141 | } 142 | 143 | self.save = function(){ 144 | userService.post({ name: self.newUser }) 145 | .then(thenHandler) 146 | .catch(catchHandler); 147 | }; 148 | 149 | self.saveName = function(){ 150 | userService.post(self.newUser) 151 | .then(thenHandler) 152 | .catch(catchHandler); 153 | }; 154 | 155 | self.addDefaultUsers = function(){ 156 | userService.post({ 157 | group: self.newUser, 158 | users: [ 159 | { name: 'One' }, 160 | { name: 'Two' } 161 | ] 162 | }).then(thenHandler).catch(catchHandler); 163 | }; 164 | 165 | function searchHandler(response){ 166 | self.foundUser = response.data.name; 167 | self.success = 'User found: ' + self.query; 168 | self.showError = false; 169 | self.error = null; 170 | } 171 | 172 | self.search = function(){ 173 | userService.getBy(self.query, self.queryCity || undefined) 174 | .then(searchHandler) 175 | .catch(catchHandler); 176 | }; 177 | 178 | self.searchById = function() { 179 | userService.getById(self.query) 180 | .then(searchHandler) 181 | .catch(catchHandler); 182 | }; 183 | 184 | self.searchByQuery = function(){ 185 | userService.getByQuery(self.query, self.queryCity) 186 | .then(searchHandler) 187 | .catch(catchHandler); 188 | }; 189 | 190 | self.searchExternal = function(){ 191 | userService.getFromExternalBy(self.query, self.queryCity) 192 | .then(searchHandler) 193 | .catch(catchHandler); 194 | }; 195 | 196 | self.getAuthenticated = function(){ 197 | userService.getAuthenticated(self.authenticated) 198 | .then(function(response){ 199 | self.users = response.data; 200 | }) 201 | .catch(catchHandler); 202 | }; 203 | }) 204 | .controller('GroupController', function(groupService){ 205 | var self = this; 206 | 207 | self.groups = []; 208 | 209 | groupService.get() 210 | .then(function(response){ 211 | self.groups = response.data; 212 | }); 213 | }) 214 | .controller('ConvenienceController', function(convenienceService){ 215 | var self = this; 216 | 217 | self.convs = []; 218 | self.showError = false; 219 | 220 | function handler(data, status){ 221 | self.data = data; 222 | self.status = status; 223 | } 224 | 225 | function successHandler(data, status){ 226 | self.showError = false; 227 | handler(data, status); 228 | } 229 | 230 | function errorHandler(data, status){ 231 | self.showError = true; 232 | handler(data, status); 233 | } 234 | 235 | convenienceService.get() 236 | .success(successHandler) 237 | .error(errorHandler); 238 | 239 | self.getMeta = function(){ 240 | convenienceService.head() 241 | .success(successHandler) 242 | .error(errorHandler); 243 | }; 244 | 245 | self.save = function(){ 246 | var dataToPost = self.input ? { test: self.input } : undefined; 247 | 248 | convenienceService.post(dataToPost) 249 | .success(successHandler) 250 | .error(errorHandler); 251 | }; 252 | 253 | self.delete = function(){ 254 | convenienceService.delete(self.id) 255 | .success(successHandler) 256 | .error(errorHandler); 257 | }; 258 | 259 | self.update = function(){ 260 | convenienceService.put(self.id, { test: self.input }) 261 | .success(successHandler) 262 | .error(errorHandler); 263 | }; 264 | 265 | self.patch = function(){ 266 | convenienceService.patch(self.id, { test: self.input }) 267 | .success(successHandler) 268 | .error(errorHandler); 269 | }; 270 | 271 | self.jsonp = function(){ 272 | convenienceService.jsonp() 273 | .success(successHandler) 274 | .error(errorHandler); 275 | }; 276 | }) 277 | .controller('HttpDefaultsController', function($http){ 278 | this.hasHttpDefaults = !!$http.defaults; 279 | }) 280 | .controller('PluginsController', function(userService){ 281 | var self = this; 282 | 283 | self.get = function(){ 284 | return userService.getThroughPlugin().then(function(response){ 285 | console.log('HERE', response); 286 | self.result = response.data; 287 | }); 288 | } 289 | }); -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Protractor Http Mock - Example 5 | 6 | 7 |
8 | {{ ctrl.name }} 9 |
10 | 11 |
12 | 19 | 20 |

{{ ctrl.error }}

21 |

{{ ctrl.success }}

22 | 23 |
24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 				{{ ctrl.foundUser }}
32 | 			
33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 |
44 | 45 | 46 |
47 |
48 | 49 |
50 | 53 |
54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {{ ctrl.data }} 67 | {{ ctrl.status }} 68 | My convenience error 69 |
70 | 71 |
72 |
$http defaults are set
73 |
74 | 75 |
76 | 77 | {{ ctrl.result }} 78 |
79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /example/mocks/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | request: { 3 | path: '/default', 4 | method: 'GET' 5 | }, 6 | response: { 7 | data: { 8 | name: "i'm always loaded" 9 | } 10 | } 11 | }; -------------------------------------------------------------------------------- /example/mocks/format.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'request': { 3 | 'path': '/groups', 4 | 'method': 'GET' 5 | }, 6 | "response": { 7 | 'data': [ { name: 'format test' }] 8 | } 9 | }; -------------------------------------------------------------------------------- /example/mocks/multiple.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | request: { 4 | path: '/users', 5 | method: 'GET' 6 | }, 7 | response: { 8 | data: [ 9 | { 10 | firstName: 'multiple', 11 | lastName: 'from file' 12 | } 13 | ] 14 | } 15 | }, 16 | { 17 | request: { 18 | path: '/groups', 19 | method: 'GET' 20 | }, 21 | response: { 22 | data: [ 23 | { name: 'file first' }, 24 | { name: 'file second' } 25 | ] 26 | } 27 | } 28 | ]; 29 | -------------------------------------------------------------------------------- /example/mocks/users.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | request: { 3 | path: '/users', 4 | method: 'GET' 5 | }, 6 | response: { 7 | data: [ 8 | { 9 | firstName: 'from', 10 | lastName: 'file' 11 | }, 12 | { 13 | firstName: 'second', 14 | lastName: 'from file' 15 | } 16 | ] 17 | } 18 | }; -------------------------------------------------------------------------------- /example/protractor-conf.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | baseUrl: 'http://localhost:8000/', 3 | specs: [ 4 | 'spec/*.spec.js' 5 | ], 6 | mocks: { 7 | dir: 'mocks', 8 | default: ['default'] 9 | }, 10 | httpMockPlugins: { 11 | default: ['protractor-http-mock-sample-plugin'] 12 | }, 13 | onPrepare: function(){ 14 | require('../index').config = { 15 | rootDirectory: __dirname 16 | } 17 | } 18 | }; 19 | 20 | if (process.env.TRAVIS) { 21 | //Run PhantomJS on Travis 22 | config.capabilities = { 23 | browserName: 'phantomjs', 24 | 'phantomjs.ghostdriver.cli.args': ['--loglevel=DEBUG'], 25 | shardTestFiles: true, 26 | maxInstances: 2 27 | }; 28 | } else { 29 | config.capabilities = { 30 | browserName: 'chrome' 31 | }; 32 | } 33 | exports.config = config; -------------------------------------------------------------------------------- /example/spec/casing.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('casing', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | it('ignores casing for request method', function(){ 11 | mock([ 12 | { 13 | request: { 14 | path: '/users', 15 | method: 'get' 16 | }, 17 | response: { 18 | data: [ 19 | { 20 | firstName: 'multiple', 21 | lastName: 'mocks' 22 | } 23 | ] 24 | } 25 | } 26 | ]); 27 | 28 | get(); 29 | 30 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 31 | expect(users.length).toBe(1); 32 | expect(users[0].getText()).toBe('multiple | mocks'); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /example/spec/convenience.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('requests made', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | function assertData(expectedData){ 11 | expect(element(by.id('conv-data')).getText()).toBe(expectedData); 12 | } 13 | 14 | function assertStatus(status){ 15 | expect(element(by.id('conv-status')).getText()).toBe(status.toString()); 16 | } 17 | 18 | function setId(val){ 19 | element(by.model('ctrl.id')).clear().sendKeys(val); 20 | } 21 | 22 | function setInput(val){ 23 | element(by.model('ctrl.input')).clear().sendKeys(val); 24 | } 25 | 26 | function clearInput(){ 27 | element(by.model('ctrl.input')).clear(); 28 | } 29 | 30 | it('works with $http convenience methods', function(){ 31 | mock([ 32 | { 33 | request: { 34 | path: '/convs', 35 | method: 'GET' 36 | }, 37 | response: { 38 | data: 'get - success!' 39 | } 40 | }, 41 | { 42 | request: { 43 | path: '/convs', 44 | method: 'HEAD' 45 | }, 46 | response: { 47 | data: 'head - success!' 48 | } 49 | }, 50 | { 51 | request: { 52 | path: 'convs/new', 53 | method: 'POST', 54 | data: { 55 | test: 'my new conv' 56 | } 57 | }, 58 | response: { 59 | data: 'post - success!', 60 | status: 201 61 | } 62 | }, 63 | { 64 | request: { 65 | path: 'convs/2', 66 | method: 'DELETE' 67 | }, 68 | response: { 69 | data: 'delete - success', 70 | status: 202 71 | } 72 | }, 73 | { 74 | request: { 75 | path: 'convs/3', 76 | method: 'DELETE' 77 | }, 78 | response: { 79 | data: 'delete - error', 80 | status: 400 81 | } 82 | }, 83 | { 84 | request: { 85 | path: 'convs/3', 86 | method: 'PUT', 87 | data: { 88 | test: 'my updated conv' 89 | } 90 | }, 91 | response: { 92 | data: 'put - success', 93 | status: 203 94 | } 95 | }, 96 | { 97 | request: { 98 | path: 'convs/3', 99 | method: 'PATCH', 100 | data: { 101 | test: 'my updated conv' 102 | } 103 | }, 104 | response: { 105 | data: 'patch - error', 106 | status: 401 107 | } 108 | }, 109 | { 110 | request: { 111 | path: 'convs/4', 112 | method: 'PATCH', 113 | data: { 114 | test: 'my patched conv' 115 | } 116 | }, 117 | response: { 118 | data: 'patch - success', 119 | status: 204 120 | } 121 | }, 122 | { 123 | request: { 124 | path: 'convs', 125 | method: 'JSONP' 126 | }, 127 | response: { 128 | data: 'jsonp - success', 129 | status: 205 130 | } 131 | } 132 | ]); 133 | 134 | // get 135 | get(); 136 | assertData('get - success!'); 137 | assertStatus(200); 138 | 139 | // head 140 | element(by.id('conv-head')).click(); 141 | assertData('head - success!'); 142 | assertStatus(200); 143 | 144 | // post 145 | setInput('my new conv'); 146 | element(by.id('conv-save')).click(); 147 | assertData('post - success!'); 148 | assertStatus(201); 149 | clearInput(); 150 | 151 | // delete 152 | setId('2'); 153 | element(by.id('conv-delete')).click(); 154 | assertData('delete - success'); 155 | assertStatus(202); 156 | 157 | // delete - error 158 | setId('3'); 159 | element(by.id('conv-delete')).click(); 160 | expect(element(by.id('conv-error')).isDisplayed()).toBe(true); 161 | assertData('delete - error'); 162 | assertStatus(400); 163 | 164 | // put 165 | setId('3'); 166 | setInput('my updated conv'); 167 | 168 | element(by.id('conv-update')).click(); 169 | expect(element(by.id('conv-error')).isDisplayed()).toBe(false); 170 | assertData('put - success'); 171 | assertStatus(203); 172 | 173 | // patch - error 174 | element(by.id('conv-patch')).click(); 175 | expect(element(by.id('conv-error')).isDisplayed()).toBe(true); 176 | assertData('patch - error'); 177 | assertStatus(401); 178 | 179 | clearInput(); 180 | 181 | // patch 182 | setId('4'); 183 | setInput('my patched conv'); 184 | element(by.id('conv-patch')).click(); 185 | assertData('patch - success'); 186 | assertStatus(204); 187 | 188 | // jsonp 189 | setId('5'); 190 | element(by.id('conv-jsonp')).click(); 191 | assertData('jsonp - success'); 192 | assertStatus(205); 193 | }); 194 | }); -------------------------------------------------------------------------------- /example/spec/default.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('default file', function(){ 5 | afterEach(function(){ 6 | mock.teardown(); 7 | }); 8 | 9 | it('loads a single mock from file', function(){ 10 | mock(); 11 | get(); 12 | expect(browser.getTitle()).toBe('Protractor Http Mock - Example'); 13 | expect($('#default-name').getText()).toBe("i'm always loaded"); 14 | }); 15 | }); -------------------------------------------------------------------------------- /example/spec/external.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('get query strings', function(){ 5 | beforeEach(function(){ 6 | mock([ 7 | { 8 | request: { 9 | path: '/users', 10 | method: 'GET' 11 | }, 12 | response: { 13 | status: 200 14 | } 15 | }, 16 | { 17 | request: { 18 | path: '/users', 19 | method: 'GET', 20 | params: { 21 | name: 'External', 22 | city: 'Query' 23 | } 24 | }, 25 | response: { 26 | data: { 27 | name: 'Searched external URL' 28 | } 29 | } 30 | } 31 | ]); 32 | }); 33 | 34 | afterEach(function(){ 35 | mock.teardown(); 36 | }); 37 | 38 | it('matches by params', function(){ 39 | get(); 40 | 41 | element(by.id('user-query')).sendKeys('External'); 42 | element(by.id('user-query-city')).sendKeys('Query'); 43 | element(by.id('user-search-external-by-query-button')).click(); 44 | 45 | expect(element(by.id('user-data')).getText()).toContain('Searched external URL'); 46 | expect(element(by.id('successMsg')).getText()).toBe('User found: External'); 47 | }); 48 | }); -------------------------------------------------------------------------------- /example/spec/fromFile.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('from file', function(){ 5 | afterEach(function(){ 6 | mock.teardown(); 7 | }); 8 | 9 | it('loads a single mock from file', function(){ 10 | mock(['users']); 11 | 12 | get(); 13 | 14 | expect(browser.getTitle()).toBe('Protractor Http Mock - Example'); 15 | 16 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 17 | expect(users.length).toBe(2); 18 | expect(users[0].getText()).toBe('from | file'); 19 | expect(users[1].getText()).toBe('second | from file'); 20 | }); 21 | }); 22 | 23 | it('loads multiple from a file', function(){ 24 | mock(['multiple']); 25 | 26 | get(); 27 | 28 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 29 | expect(users.length).toBe(1); 30 | expect(users[0].getText()).toBe('multiple | from file'); 31 | }); 32 | 33 | element.all(by.repeater('group in ctrl.groups')).then(function(groups){ 34 | expect(groups.length).toBe(2); 35 | expect(groups[0].getText()).toBe('file first'); 36 | expect(groups[1].getText()).toBe('file second'); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /example/spec/get.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | browser.get('http://localhost:8000'); 3 | }; -------------------------------------------------------------------------------- /example/spec/headers.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('headers', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | it('matchers with headers', function(){ 11 | mock([ 12 | { 13 | request: { 14 | path: '/users', 15 | method: 'get' 16 | }, 17 | response: { 18 | status: 403, 19 | data: { 20 | error: 'not authorized' 21 | } 22 | } 23 | }, 24 | { 25 | request: { 26 | path: '/users', 27 | method: 'get', 28 | headers: { 29 | auth: true 30 | } 31 | }, 32 | response: { 33 | data: [ 34 | { 35 | firstName: 'auth', 36 | lastName: 'one' 37 | }, 38 | { 39 | firstName: 'auth', 40 | lastName: 'two' 41 | } 42 | ] 43 | } 44 | } 45 | ]); 46 | 47 | get(); 48 | 49 | element(by.id('user-get-authenticated-button')).click(); 50 | 51 | var users = element.all(by.repeater('user in ctrl.users')); 52 | 53 | expect(users.count()).toBe(0); 54 | var errElement = element(by.binding('ctrl.error')); 55 | expect(errElement.isDisplayed()).toBe(true); 56 | expect(errElement.getText()).toBe('not authorized'); 57 | 58 | element(by.id('user-select-authenticated')).click(); 59 | element(by.id('user-get-authenticated-button')).click(); 60 | 61 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 62 | expect(users.length).toBe(2); 63 | expect(users[0].getText()).toBe('auth | one'); 64 | expect(users[1].getText()).toBe('auth | two'); 65 | }); 66 | }); 67 | }); -------------------------------------------------------------------------------- /example/spec/httpDefaults.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('http defaults', function(){ 5 | afterEach(function(){ 6 | mock.teardown(); 7 | }); 8 | 9 | it('loads http defaults', function(){ 10 | mock(); 11 | get(); 12 | expect(element(by.id('http-defaults')).isDisplayed()).toBe(true); 13 | }); 14 | }); -------------------------------------------------------------------------------- /example/spec/inline.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('inline', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | describe('success', function(){ 11 | it('can set a single mock inline', function(){ 12 | mock([{ 13 | request: { 14 | path: '/users', 15 | method: 'GET' 16 | }, 17 | response: { 18 | data: [ 19 | { 20 | firstName: 'carlos', 21 | lastName: 'npm' 22 | }, 23 | { 24 | firstName: 'angular', 25 | lastName: 'js' 26 | } 27 | ] 28 | } 29 | }]); 30 | 31 | get(); 32 | expect(browser.getTitle()).toBe('Protractor Http Mock - Example'); 33 | 34 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 35 | expect(users.length).toBe(2); 36 | expect(users[0].getText()).toBe('carlos | npm'); 37 | expect(users[1].getText()).toBe('angular | js'); 38 | }); 39 | 40 | expect(element(by.binding('ctrl.error')).isDisplayed()).toBe(false); 41 | }); 42 | 43 | it('can set multiple mocks inline', function(){ 44 | mock([ 45 | { 46 | request: { 47 | path: '/users', 48 | method: 'GET' 49 | }, 50 | response: { 51 | data: [ 52 | { 53 | firstName: 'multiple', 54 | lastName: 'mocks' 55 | } 56 | ] 57 | } 58 | }, 59 | { 60 | request: { 61 | path: '/groups', 62 | method: 'GET' 63 | }, 64 | response: { 65 | data: [ 66 | { name: 'first' }, 67 | { name: 'second' } 68 | ] 69 | } 70 | }, 71 | { 72 | request: { 73 | path: 'users/new', 74 | method: 'POST', 75 | data: { 76 | name: 'my-new-user' 77 | } 78 | }, 79 | response: { 80 | status: 200 81 | } 82 | } 83 | ]); 84 | 85 | get(); 86 | 87 | element.all(by.repeater('user in ctrl.users')).then(function(users){ 88 | expect(users.length).toBe(1); 89 | expect(users[0].getText()).toBe('multiple | mocks'); 90 | }); 91 | 92 | element.all(by.repeater('group in ctrl.groups')).then(function(groups){ 93 | expect(groups.length).toBe(2); 94 | expect(groups[0].getText()).toBe('first'); 95 | expect(groups[1].getText()).toBe('second'); 96 | }); 97 | 98 | expect(element(by.binding('ctrl.error')).isDisplayed()).toBe(false); 99 | 100 | element(by.model('ctrl.newUser')).sendKeys('my-new-user'); 101 | element(by.css('.form #save')).click(); 102 | 103 | var succElement = element(by.binding('ctrl.success')); 104 | expect(succElement.isDisplayed()).toBe(true); 105 | expect(succElement.getText()).toBe('new user saved: my-new-user'); 106 | }); 107 | }); 108 | 109 | describe('error', function(){ 110 | it('can set a single mock inline', function(){ 111 | mock([{ 112 | request: { 113 | path: '/users', 114 | method: 'GET' 115 | }, 116 | response: { 117 | status: 500, 118 | data: { 119 | error: 'oh no!' 120 | } 121 | } 122 | }]); 123 | 124 | get(); 125 | var users = element.all(by.repeater('user in ctrl.users')); 126 | expect(users.count()).toBe(0); 127 | var errElement = element(by.binding('ctrl.error')); 128 | expect(errElement.isDisplayed()).toBe(true); 129 | expect(errElement.getText()).toBe('oh no!'); 130 | }); 131 | 132 | it('can set multiple mocks inline', function(){ 133 | mock([ 134 | { 135 | request: { 136 | path: '/users', 137 | method: 'GET' 138 | }, 139 | response: { 140 | status: 500, 141 | data: { 142 | error: 'help!' 143 | } 144 | } 145 | }, 146 | { 147 | request: { 148 | path: '/groups', 149 | method: 'GET' 150 | }, 151 | response: { 152 | status: 200, 153 | data: [ { name: 'i did work' }] 154 | } 155 | }, 156 | { 157 | request: { 158 | path: '/users/new', 159 | method: 'POST', 160 | data: {} 161 | }, 162 | response: { 163 | status: 500, 164 | data: { 165 | error: 'post error!' 166 | } 167 | } 168 | } 169 | ]); 170 | 171 | get(); 172 | 173 | var users = element.all(by.repeater('user in ctrl.users')); 174 | expect(users.count()).toBe(0); 175 | 176 | var groups = element.all(by.repeater('group in ctrl.groups')); 177 | expect(groups.count()).toBe(1); 178 | 179 | var errElement = element(by.binding('ctrl.error')); 180 | expect(errElement.isDisplayed()).toBe(true); 181 | expect(errElement.getText()).toBe('help!'); 182 | 183 | element(by.css('.form #save')).click(); 184 | expect(errElement.getText()).toBe('post error!'); 185 | }); 186 | }); 187 | }); -------------------------------------------------------------------------------- /example/spec/jsonFormat.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('from file', function(){ 5 | afterEach(function(){ 6 | mock.teardown(); 7 | }); 8 | 9 | it('loads a single mock from file with double quotes', function(){ 10 | mock(['format']); 11 | 12 | get(); 13 | element.all(by.repeater('group in ctrl.groups')).then(function(groups){ 14 | expect(groups.length).toBe(1); 15 | expect(groups[0].getText()).toBe('format test'); 16 | }); 17 | }); 18 | }); -------------------------------------------------------------------------------- /example/spec/params.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('get by params', function(){ 5 | beforeEach(function(){ 6 | mock([ 7 | { 8 | request: { 9 | path: '/users', 10 | method: 'GET' 11 | }, 12 | response: { 13 | status: 200 14 | } 15 | }, 16 | { 17 | request: { 18 | path: '/users', 19 | method: 'GET', 20 | params: { 21 | name: 'unknown' 22 | } 23 | }, 24 | response: { 25 | status: 500, 26 | data: { 27 | error: 'no user found' 28 | } 29 | } 30 | }, 31 | { 32 | request: { 33 | path: '/users', 34 | method: 'GET', 35 | params: { 36 | name: 'Charlie' 37 | } 38 | }, 39 | response: { 40 | data: { 41 | name: 'Charlie' 42 | } 43 | } 44 | }, 45 | { 46 | request: { 47 | path: '/users', 48 | method: 'GET', 49 | params: { 50 | name: 'Charlie', 51 | city: 'Houston' 52 | } 53 | }, 54 | response: { 55 | data: { 56 | name: 'Charlie in Houston' 57 | } 58 | } 59 | } 60 | ]); 61 | }); 62 | 63 | afterEach(function(){ 64 | mock.teardown(); 65 | }); 66 | 67 | it('matches by params', function(){ 68 | get(); 69 | 70 | var searchButton = element(by.id('user-search-button')); 71 | 72 | element(by.id('user-query')).sendKeys('Charlie'); 73 | searchButton.click(); 74 | 75 | expect(element(by.id('user-data')).getText()).toContain('Charlie'); 76 | expect(element(by.id('successMsg')).getText()).toBe('User found: Charlie'); 77 | 78 | element(by.id('user-query-city')).sendKeys('Houston'); 79 | searchButton.click(); 80 | 81 | expect(element(by.id('user-data')).getText()).toContain('Charlie in Houston'); 82 | expect(element(by.id('successMsg')).getText()).toBe('User found: Charlie'); 83 | 84 | element(by.id('user-query')).clear().sendKeys('unknown'); 85 | element(by.id('user-query-city')).clear(); 86 | searchButton.click(); 87 | 88 | var errElement = element(by.id('errorMsg')); 89 | expect(errElement.isDisplayed()).toBe(true); 90 | expect(errElement.getText()).toBe('no user found'); 91 | }); 92 | }); -------------------------------------------------------------------------------- /example/spec/plugin.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('requests made', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | beforeEach(function(){ 11 | mock([ 12 | { 13 | request: { 14 | path: '/users', 15 | method: 'GET', 16 | plugin: { 17 | check: true 18 | } 19 | }, 20 | response: { 21 | data: 'from sample plugin match' 22 | } 23 | }, 24 | { 25 | request: { 26 | path: '/users', 27 | method: 'GET', 28 | plugin: { 29 | check: false 30 | } 31 | }, 32 | response: { 33 | data: 'my plugin should not match here!' 34 | } 35 | } 36 | ]); 37 | 38 | get(); 39 | }); 40 | 41 | it('can match through plugin', function(){ 42 | element(by.id('plugin-request')).click(); 43 | expect(element(by.id('plugin-result')).getText()).toBe('from sample plugin match'); 44 | }); 45 | }); -------------------------------------------------------------------------------- /example/spec/postData.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('post data', function(){ 5 | afterEach(function(){ 6 | mock.teardown(); 7 | }); 8 | 9 | it('matches post data', function(){ 10 | mock([ 11 | { 12 | request: { 13 | path: 'users/new', 14 | method: 'POST' 15 | }, 16 | response: { 17 | status: 400, 18 | data: { 19 | error: 'generic response' 20 | } 21 | } 22 | }, 23 | { 24 | request: { 25 | path: 'users/new', 26 | method: 'POST', 27 | data: { 28 | name: 'Stewie' 29 | } 30 | }, 31 | response: { 32 | status: 200 33 | } 34 | }, 35 | { 36 | request: { 37 | path: 'users/new', 38 | method: 'POST', 39 | data: 'Brian' 40 | }, 41 | response: { 42 | status: 500, 43 | data: { 44 | error: 'no dogs allowed!' 45 | } 46 | } 47 | } 48 | ]); 49 | 50 | get(); 51 | 52 | var succElement = element(by.binding('ctrl.success')); 53 | var errElement = element(by.binding('ctrl.error')); 54 | 55 | element(by.css('.form #save')).click(); 56 | 57 | expect(succElement.isDisplayed()).toBe(false); 58 | expect(errElement.isDisplayed()).toBe(true); 59 | expect(errElement.getText()).toBe('generic response'); 60 | 61 | element(by.model('ctrl.newUser')).sendKeys('Stewie'); 62 | element(by.css('.form #save')).click(); 63 | expect(succElement.isDisplayed()).toBe(true); 64 | expect(errElement.isDisplayed()).toBe(false); 65 | expect(succElement.getText()).toBe('new user saved: Stewie'); 66 | 67 | element(by.model('ctrl.newUser')).sendKeys('Brian'); 68 | element(by.css('.form #save-name')).click(); 69 | expect(succElement.isDisplayed()).toBe(false); 70 | expect(errElement.isDisplayed()).toBe(true); 71 | expect(errElement.getText()).toBe('no dogs allowed!'); 72 | }); 73 | 74 | it('matches complex post data', function(){ 75 | mock([ 76 | { 77 | request: { 78 | path: 'users/new', 79 | method: 'POST', 80 | data: { 81 | group: 'other group' 82 | } 83 | }, 84 | response: { 85 | status: 500 86 | } 87 | }, 88 | { 89 | request: { 90 | path: 'users/new', 91 | method: 'POST', 92 | data: { 93 | group: 'default', 94 | users: [ 95 | { name: 'One' }, 96 | { name: 'Two' } 97 | ] 98 | } 99 | }, 100 | response: { 101 | status: 200 102 | } 103 | } 104 | ]); 105 | 106 | get(); 107 | 108 | element(by.model('ctrl.newUser')).sendKeys('default'); 109 | element(by.css('.form #add-default')).click(); 110 | 111 | var succElement = element(by.binding('ctrl.success')); 112 | expect(succElement.isDisplayed()).toBe(true); 113 | expect(succElement.getText()).toBe('new user saved: default'); 114 | }); 115 | }); -------------------------------------------------------------------------------- /example/spec/queryString.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('get query strings', function(){ 5 | beforeEach(function(){ 6 | mock([ 7 | { 8 | request: { 9 | path: '/users', 10 | method: 'GET' 11 | }, 12 | response: { 13 | status: 200, 14 | data: [] 15 | } 16 | }, 17 | { 18 | request: { 19 | path: '/users', 20 | method: 'GET', 21 | queryString: { 22 | name: 'Charlie & Friends', 23 | city: 'New York' 24 | } 25 | }, 26 | response: { 27 | data: { 28 | name: 'Charlie' 29 | } 30 | } 31 | }, 32 | { 33 | request: { 34 | path: '/users', 35 | method: 'GET', 36 | queryString: { 37 | name: 'Maria C', 38 | city: 'Houston' 39 | } 40 | }, 41 | response: { 42 | data: { 43 | name: 'Maria in Houston' 44 | } 45 | } 46 | } 47 | ]); 48 | }); 49 | 50 | afterEach(function(){ 51 | mock.teardown(); 52 | }); 53 | 54 | it('matches by params', function(){ 55 | get(); 56 | 57 | var searchButton = element(by.id('user-search-by-query-button')); 58 | 59 | element(by.id('user-query')).sendKeys('Charlie & Friends'); 60 | element(by.id('user-query-city')).sendKeys('New York'); 61 | searchButton.click(); 62 | 63 | expect(element(by.id('user-data')).getText()).toContain('Charlie'); 64 | expect(element(by.id('successMsg')).getText()).toBe('User found: Charlie & Friends'); 65 | 66 | element(by.id('user-query')).clear().sendKeys('Maria C'); 67 | element(by.id('user-query-city')).clear().sendKeys('Houston'); 68 | searchButton.click(); 69 | 70 | expect(element(by.id('user-data')).getText()).toContain('Maria in Houston'); 71 | expect(element(by.id('successMsg')).getText()).toBe('User found: Maria C'); 72 | }); 73 | }); -------------------------------------------------------------------------------- /example/spec/regex.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), 2 | get = require('./get'); 3 | 4 | describe('match path with regex', function(){ 5 | beforeEach(function(){ 6 | mock([ 7 | { 8 | request: { 9 | path: '\\/users\\/[0-9]', 10 | method: 'GET', 11 | regex: true 12 | }, 13 | response: { 14 | status: 200, 15 | data: { 16 | name: 'User with int id' 17 | } 18 | } 19 | }, 20 | { 21 | request: { 22 | path: '\\/users\\/[^0-9]', 23 | method: 'GET', 24 | regex: true 25 | }, 26 | response: { 27 | status: 400, 28 | data: { 29 | error: 'Not a number' 30 | } 31 | } 32 | } 33 | ]); 34 | }); 35 | 36 | afterEach(function(){ 37 | mock.teardown(); 38 | }); 39 | 40 | it('match on int id', function(){ 41 | get(); 42 | 43 | var searchButton = element(by.id('user-search-by-id-button')); 44 | 45 | element(by.id('user-query')).sendKeys('1'); 46 | searchButton.click(); 47 | 48 | expect(element(by.id('user-data')).getText()).toContain('User with int id'); 49 | 50 | element(by.id('user-query')).clear().sendKeys('abc'); 51 | searchButton.click(); 52 | 53 | expect(element(by.id('errorMsg')).getText()).toBe('Not a number'); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /example/spec/requestsMade.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('requests made', function(){ 5 | 6 | afterEach(function(){ 7 | mock.teardown(); 8 | }); 9 | 10 | beforeEach(function(){ 11 | mock([ 12 | { 13 | request: { 14 | path: '/users', 15 | method: 'GET' 16 | }, 17 | response: { 18 | data: [ 19 | { 20 | firstName: 'carlos', 21 | lastName: 'npm' 22 | }, 23 | { 24 | firstName: 'angular', 25 | lastName: 'js' 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | request: { 32 | path: 'users/new', 33 | method: 'POST', 34 | data: { 35 | name: 'my-new-user' 36 | } 37 | }, 38 | response: { 39 | status: 200 40 | } 41 | } 42 | ]); 43 | 44 | get(); 45 | }); 46 | 47 | it('can evaluate requests made', function(){ 48 | expect(mock.requestsMade()).toEqual([ 49 | { url : '/default', method : 'GET' }, 50 | { url : '/users', method : 'GET' } 51 | ]); 52 | 53 | element(by.model('ctrl.newUser')).sendKeys('my-new-user'); 54 | element(by.css('.form #save')).click(); 55 | 56 | expect(mock.requestsMade()).toEqual([ 57 | { url : '/default', method : 'GET' }, 58 | { url : '/users', method : 'GET' }, 59 | { data : { name : 'my-new-user' }, url : '/users/new', method : 'POST' } 60 | ]); 61 | }); 62 | 63 | it('can evaluate just the last request made', function(){ 64 | mock.requestsMade().then(function(requests){ 65 | expect(requests[1]).toEqual({ url : '/users', method : 'GET' }) 66 | }); 67 | }); 68 | 69 | it('can clear requests', function(){ 70 | mock.clearRequests(); 71 | expect(mock.requestsMade()).toEqual([]); 72 | 73 | element(by.model('ctrl.newUser')).sendKeys('my-new-user'); 74 | element(by.css('.form #save')).click(); 75 | 76 | mock.clearRequests(); 77 | expect(mock.requestsMade()).toEqual([]); 78 | }); 79 | }); -------------------------------------------------------------------------------- /example/spec/runtime.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('../../index'), //substitute for require('protractor-http-mock') 2 | get = require('./get'); 3 | 4 | describe('inline', function(){ 5 | 6 | beforeEach(function(){ 7 | mock([ 8 | { 9 | request: { 10 | path: '/users', 11 | method: 'GET' 12 | }, 13 | response: { 14 | data: [ 15 | { 16 | firstName: 'default', 17 | lastName: 'value' 18 | } 19 | ] 20 | } 21 | }, 22 | { 23 | request: { 24 | path: '/users', 25 | method: 'GET', 26 | params: { 27 | name: 'Charlie' 28 | } 29 | }, 30 | response: { 31 | data: { 32 | name: 'Setup' 33 | } 34 | } 35 | } 36 | ]); 37 | 38 | get(); 39 | 40 | mock.add([{ 41 | request: { 42 | path: '/users', 43 | method: 'GET', 44 | params: { 45 | name: 'Charlie' 46 | } 47 | }, 48 | response: { 49 | data: { 50 | name: 'Override' 51 | } 52 | } 53 | }]); 54 | }); 55 | 56 | afterEach(function(){ 57 | mock.teardown(); 58 | }); 59 | 60 | it('can add mocks', function(){ 61 | element(by.id('user-query')).clear().sendKeys('Charlie'); 62 | element(by.id('user-search-button')).click(); 63 | 64 | expect(element(by.id('user-data')).getText()).toContain('Override'); 65 | }); 66 | 67 | it('can remove mocks', function(){ 68 | mock.remove([{ 69 | request: { 70 | path: '/users', 71 | method: 'GET', 72 | params: { 73 | name: 'Charlie' 74 | } 75 | }, 76 | response: { 77 | data: { 78 | name: 'Override' 79 | } 80 | } 81 | }]); 82 | 83 | element(by.id('user-query')).clear().sendKeys('Charlie'); 84 | element(by.id('user-search-button')).click(); 85 | 86 | expect(element(by.id('user-data')).getText()).toContain('Setup'); 87 | }); 88 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/initData'); -------------------------------------------------------------------------------- /lib/defaultConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDirectory: process.cwd(), 3 | protractorConfig: 'protractor-conf.js', 4 | 5 | mocks: { 6 | dir: 'mocks', 7 | default: [] 8 | }, 9 | 10 | plugins: { 11 | default: [] 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /lib/httpMock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var queryString = require('query-string'); 4 | 5 | function mockTemplate() { 6 | var queryStringParse = ''; 7 | var expectations = ''; 8 | var plugins = ''; 9 | 10 | var newModule = angular.module('httpMock', []); 11 | 12 | newModule.requests = []; 13 | 14 | newModule.config(['$provide', '$httpProvider', function($provide, $httpProvider){ 15 | 16 | $provide.decorator('$http', ['$delegate', '$q', '$injector', function($http, $q, $injector) { 17 | 18 | var interceptors = []; 19 | 20 | angular.forEach($httpProvider.interceptors, function(interceptorExpression) { 21 | var interceptor; 22 | if (angular.isString(interceptorExpression)) { 23 | interceptor = $injector.get(interceptorExpression); 24 | } else { 25 | interceptor = $injector.invoke(interceptorExpression); 26 | } 27 | interceptors.push(interceptor); 28 | }); 29 | 30 | function transformData(data, headers, status, fns) { 31 | if (typeof fns === 'function') { 32 | data = fns(data, headers, status); 33 | } else { 34 | for (var i = 0; i < fns.length; i++) { 35 | data = fns[i](data, headers, status); 36 | } 37 | } 38 | 39 | return data; 40 | } 41 | 42 | function transformRequest(requestConfig){ 43 | if (requestConfig.transformRequest) { 44 | requestConfig.data = transformData(requestConfig.data, 45 | requestConfig.headers, 46 | undefined, 47 | requestConfig.transformRequest); 48 | } 49 | 50 | return requestConfig; 51 | } 52 | 53 | function getTransformedAndInterceptedRequestConfig(requestConfig) { 54 | for (var i = 0; i < interceptors.length; i++) { 55 | var interceptor = interceptors[i]; 56 | 57 | if (interceptor.request) { 58 | $q.when(interceptor.request(requestConfig)).then(function(interceptedRequestConfig){ 59 | requestConfig = interceptedRequestConfig; 60 | }); 61 | } 62 | } 63 | 64 | requestConfig = transformRequest(requestConfig); 65 | 66 | return requestConfig; 67 | } 68 | 69 | function transformResponse(response) { 70 | if (response.config.transformResponse) { 71 | response.data = transformData(response.data, 72 | response.headers, 73 | response.status, 74 | response.config.transformResponse); 75 | } 76 | 77 | return response; 78 | } 79 | 80 | function statusIsSuccessful(status){ 81 | return status >= 200 && status <= 299; 82 | } 83 | 84 | function getTransformedAndInterceptedResponse(response) { 85 | response = transformResponse(response); 86 | 87 | // Response interceptors are invoked in reverse order as per docs 88 | for (var i = interceptors.length - 1; i >= 0; i--) { 89 | var interceptor = interceptors[i]; 90 | 91 | if (interceptor.response && statusIsSuccessful(response.status)) { 92 | $q.when(interceptor.response(response)).then(function(interceptedResponse){ 93 | response = interceptedResponse; 94 | }); 95 | } 96 | 97 | if (interceptor.responseError && !statusIsSuccessful(response.status)) { 98 | $q.reject(interceptor.responseError(response)).then(null, function(interceptedResponse){ 99 | response = interceptedResponse; 100 | }); 101 | } 102 | } 103 | 104 | return response; 105 | } 106 | 107 | function matchRegex(pattern, string){ 108 | var regex = new RegExp(pattern); 109 | return regex.test(string); 110 | } 111 | 112 | function endsWith(url, path){ 113 | var questionMarkIndex = url.indexOf('?'); 114 | 115 | if(questionMarkIndex < 0){ 116 | return url.indexOf(path, url.length - path.length) !== -1; 117 | }else{ 118 | var noQueryStringUrl = url.substring(0, questionMarkIndex); 119 | return endsWith(noQueryStringUrl, path); 120 | } 121 | } 122 | 123 | function matchProperty(property, expectationRequest, config){ 124 | return !expectationRequest[property] || angular.equals(expectationRequest[property], config[property]); 125 | } 126 | 127 | function matchParams(expectationRequest, config){ 128 | return matchProperty('params', expectationRequest, config); 129 | } 130 | 131 | function matchData(expectationRequest, config){ 132 | return matchProperty('data', expectationRequest, config); 133 | } 134 | 135 | function matchHeaders(expectationRequest, config){ 136 | var simplifiedConfig = angular.copy(config); 137 | 138 | if(simplifiedConfig.headers){ 139 | var headers = simplifiedConfig.headers; 140 | 141 | for(var prop in headers){ 142 | if(headers.hasOwnProperty(prop) && typeof headers[prop] === 'function'){ 143 | headers[prop] = headers[prop](config); 144 | 145 | if(headers[prop] === null){ 146 | delete headers[prop]; 147 | } 148 | } 149 | } 150 | } 151 | 152 | return matchProperty('headers', expectationRequest, simplifiedConfig); 153 | } 154 | 155 | function matchQueryString(expectationRequest, config){ 156 | var match = true, 157 | url = config.url; 158 | 159 | var queryStringStartIndex = url.indexOf('?'); 160 | 161 | if(expectationRequest.queryString && queryStringStartIndex > -1){ 162 | var qsParams = queryStringParse(url.substring(queryStringStartIndex, url.length)); 163 | match = angular.equals(expectationRequest.queryString, qsParams); 164 | } 165 | 166 | return match; 167 | } 168 | 169 | function matchMethod(expectationRequest, config){ 170 | var configMethod = config.method ? config.method.toLowerCase() : 'get'; 171 | return expectationRequest.method.toLowerCase() === configMethod; 172 | } 173 | 174 | function matchByPlugins(expectationRequest, config){ 175 | var match = true; 176 | 177 | if(plugins.length > 0){ 178 | match = plugins.reduce(function(value, plugin){ 179 | return plugin(expectationRequest, config) && value; 180 | }, true); 181 | } 182 | 183 | return match; 184 | } 185 | 186 | function match(config, expectationRequest){ 187 | return matchMethod(expectationRequest, config) && 188 | (expectationRequest.regex ? matchRegex(expectationRequest.path, config.url) 189 | : endsWith(config.url, expectationRequest.path)) && 190 | matchParams(expectationRequest, config) && 191 | matchData(expectationRequest, config) && 192 | matchQueryString(expectationRequest, config) && 193 | matchHeaders(expectationRequest, config) && 194 | matchByPlugins(expectationRequest, config); 195 | } 196 | 197 | function matchExpectation(config){ 198 | var expectation; 199 | 200 | for(var i = 0; i < expectations.length; i++){ 201 | if(match(config, expectations[i].request)){ 202 | expectation = expectations[i]; 203 | } 204 | } 205 | 206 | return expectation; 207 | } 208 | 209 | function wrapWithSuccessError(promise) { 210 | var myPromise = promise; 211 | 212 | myPromise.success = function(callback) { 213 | myPromise.then(function(response) { 214 | callback(response.data, response.status, response.headers, response.config); 215 | }); 216 | return myPromise; 217 | }; 218 | 219 | myPromise.error = function(callback) { 220 | myPromise.then(null, function(response) { 221 | callback(response.data, response.status, response.headers, response.config); 222 | }); 223 | return myPromise; 224 | }; 225 | 226 | return myPromise; 227 | } 228 | 229 | function createHeaderGetterFunction(responseHeaders){ 230 | return function(headerName) { 231 | if (!headerName) { 232 | return responseHeaders; 233 | } 234 | 235 | return responseHeaders[headerName]; 236 | }; 237 | } 238 | 239 | function addToRequestHistory(config){ 240 | var copy = angular.copy(config); 241 | 242 | // This is done to maintain backwards compatability 243 | // as well as providing a cleaner request history 244 | if(angular.equals(copy.headers, {})){ 245 | delete copy.headers; 246 | } 247 | 248 | newModule.requests.push(copy); 249 | } 250 | 251 | function httpMock(config){ 252 | var prom; 253 | 254 | config.headers = config.headers || {}; 255 | var transformedConfig = getTransformedAndInterceptedRequestConfig(angular.copy(config)); 256 | 257 | return wrapWithSuccessError($q.when(transformedConfig).then(function(resolvedConfig) { 258 | var expectation = matchExpectation(resolvedConfig); 259 | 260 | if(expectation){ 261 | var deferred = $q.defer(); 262 | 263 | addToRequestHistory(resolvedConfig); 264 | 265 | var delay = expectation.response.delay || 0; 266 | 267 | setTimeout(function(){ 268 | var resolvedResponse; 269 | 270 | expectation.response = expectation.response || {}; 271 | 272 | // Important to clone the response so that interceptors don't change the expectation response 273 | resolvedResponse = angular.copy(expectation.response); 274 | 275 | resolvedResponse.config = resolvedConfig; 276 | 277 | if(resolvedResponse.headers){ 278 | resolvedResponse.headers = createHeaderGetterFunction(resolvedResponse.headers); 279 | }else{ 280 | resolvedResponse.headers = function () {}; 281 | } 282 | 283 | resolvedResponse.status = resolvedResponse.status || 200; 284 | resolvedResponse = getTransformedAndInterceptedResponse(resolvedResponse); 285 | 286 | $q.when(resolvedResponse).then(function(resolvedResponse) { 287 | if (statusIsSuccessful(resolvedResponse.status)) { 288 | deferred.resolve(resolvedResponse); 289 | } else { 290 | deferred.reject(resolvedResponse); 291 | } 292 | }); 293 | }, delay); 294 | 295 | prom = deferred.promise; 296 | } else { 297 | prom = $http(config); 298 | } 299 | 300 | return prom; 301 | })); 302 | } 303 | 304 | httpMock.get = function(url, config){ 305 | config = config || {}; 306 | config.url = url; 307 | config.method = 'GET'; 308 | 309 | return httpMock(config); 310 | }; 311 | 312 | httpMock.delete = function(url, config){ 313 | config = config || {}; 314 | config.url = url; 315 | config.method = 'DELETE'; 316 | 317 | return httpMock(config); 318 | }; 319 | 320 | httpMock.head = function(url, config){ 321 | config = config || {}; 322 | config.url = url; 323 | config.method = 'HEAD'; 324 | 325 | return httpMock(config); 326 | }; 327 | 328 | httpMock.jsonp = function(url, config){ 329 | config = config || {}; 330 | config.url = url; 331 | config.method = 'JSONP'; 332 | 333 | return httpMock(config); 334 | }; 335 | 336 | httpMock.post = function(url, data, config){ 337 | config = config || {}; 338 | config.url = url; 339 | config.data = data; 340 | config.method = 'POST'; 341 | 342 | return httpMock(config); 343 | }; 344 | 345 | httpMock.put = function(url, data, config){ 346 | config = config || {}; 347 | config.url = url; 348 | config.data = data; 349 | config.method = 'PUT'; 350 | 351 | return httpMock(config); 352 | }; 353 | 354 | httpMock.patch = function(url, data, config){ 355 | config = config || {}; 356 | config.url = url; 357 | config.data = data; 358 | config.method = 'PATCH'; 359 | 360 | return httpMock(config); 361 | }; 362 | 363 | httpMock.defaults = $http.defaults; 364 | 365 | return httpMock; 366 | }]);}]); 367 | 368 | newModule.clearRequests = function(){ 369 | newModule.requests = []; 370 | }; 371 | 372 | newModule.addMocks = function(expectationsToAdd){ 373 | expectations = expectations.concat(expectationsToAdd); 374 | }; 375 | 376 | newModule.removeMocks = function(expectationsToRemove){ 377 | expectations.forEach(function(expectation, index) { 378 | expectationsToRemove.forEach(function(expectationToRemove) { 379 | if (angular.equals(expectationToRemove, expectation)) { 380 | expectations.splice(index, 1); 381 | } 382 | }); 383 | }); 384 | }; 385 | 386 | return newModule; 387 | } 388 | 389 | function getExpectationsString(expectations){ 390 | var printExpectations = []; 391 | 392 | for(var i=0; i< expectations.length; i++){ 393 | printExpectations.push(JSON.stringify(expectations[i])); 394 | } 395 | 396 | return printExpectations.toString(); 397 | } 398 | 399 | function getPluginsString(plugins){ 400 | if(plugins){ 401 | var pluginStrings = []; 402 | 403 | plugins.forEach(function(plugin){ 404 | pluginStrings.push(plugin.match.toString()); 405 | }); 406 | 407 | return pluginStrings.join(); 408 | } else{ 409 | return ''; 410 | } 411 | } 412 | 413 | module.exports = function(expectations, plugins){ 414 | var templateString = mockTemplate.toString(); 415 | var template = templateString.substring(templateString.indexOf('{') + 1, templateString.lastIndexOf('}')); 416 | var pluginsString = getPluginsString(plugins); 417 | 418 | var newFunc = 419 | template 420 | .replace(/''/, '[' + getExpectationsString(expectations) + ']') 421 | .replace(/''/, queryString.parse.toString()) 422 | .replace(/''/, '[' + pluginsString + ']'); 423 | 424 | /*jslint evil: true */ 425 | return new Function(newFunc); 426 | }; 427 | -------------------------------------------------------------------------------- /lib/initData.js: -------------------------------------------------------------------------------- 1 | /* globals browser */ 2 | 'use strict'; 3 | 4 | var httpMock = require('./httpMock'), 5 | path = require('path'), 6 | defaultConfig = require('./defaultConfig'); 7 | 8 | function getConfig(){ 9 | var config = defaultConfig; 10 | 11 | if(module.exports.config){ 12 | config.rootDirectory = module.exports.config.rootDirectory || config.rootDirectory; 13 | config.protractorConfig = module.exports.config.protractorConfig || config.protractorConfig; 14 | } 15 | 16 | var protractorConfigFile = path.join(config.rootDirectory, config.protractorConfig); 17 | var protractorConfig = require(protractorConfigFile).config; 18 | 19 | config.mocks = protractorConfig.mocks || defaultConfig.mocks; 20 | // TODO: add validation check 21 | config.mocks.default = config.mocks.default || []; 22 | 23 | config.plugins = protractorConfig.httpMockPlugins || defaultConfig.plugins; 24 | // TODO: add validation check 25 | config.plugins.default = config.plugins.default || []; 26 | 27 | return config; 28 | } 29 | 30 | function readMockFile(mockDirectory, mock){ 31 | return require(path.join(mockDirectory, mock)); 32 | } 33 | 34 | function buildMocks(mocks, skipDefaults){ 35 | var data = [], 36 | config = getConfig(), 37 | mockDirectory = path.join(config.rootDirectory, config.mocks.dir); 38 | 39 | mocks = mocks || []; 40 | 41 | if(!skipDefaults){ 42 | mocks = config.mocks.default.concat(mocks); 43 | } 44 | 45 | for(var i = 0; i < mocks.length; i++){ 46 | // TODO: add validation check 47 | var dataModule = typeof mocks[i] === 'string' ? readMockFile(mockDirectory, mocks[i]) : mocks[i]; 48 | 49 | if(Array.isArray(dataModule)){ 50 | data = data.concat(dataModule); 51 | }else{ 52 | data.push(dataModule); 53 | } 54 | 55 | } 56 | 57 | return data; 58 | } 59 | 60 | function buildPlugins(plugins, skipDefaults){ 61 | var data = [], 62 | config = getConfig(); 63 | 64 | plugins = plugins || []; 65 | 66 | if(!skipDefaults){ 67 | plugins = config.plugins.default.concat(plugins); 68 | } 69 | 70 | for(var i = 0; i < plugins.length; i++){ 71 | // TODO: add validation check 72 | var plugin = typeof plugins[i] === 'string' ? require(plugins[i]) : plugins[i]; 73 | data.push(plugin); 74 | } 75 | 76 | return data; 77 | } 78 | 79 | function getProtractorInstance(){ 80 | return protractor.getInstance ? protractor.getInstance() : browser; 81 | } 82 | 83 | module.exports = function(mocks, plugins, skipDefaults){ 84 | var builtMocks = buildMocks(mocks, skipDefaults), 85 | builtPlugins = buildPlugins(plugins); 86 | 87 | var ptor = getProtractorInstance(); 88 | ptor.addMockModule('httpMock', httpMock(builtMocks, builtPlugins)); 89 | }; 90 | 91 | module.exports.teardown = function(){ 92 | var ptor = getProtractorInstance(); 93 | ptor.removeMockModule('httpMock'); 94 | }; 95 | 96 | module.exports.requestsMade = function() { 97 | return browser.executeAsyncScript(function () { 98 | var httpMock = angular.module('httpMock'); 99 | var callback = arguments[arguments.length - 1]; 100 | callback(httpMock.requests); 101 | }); 102 | }; 103 | 104 | module.exports.clearRequests = function(){ 105 | return browser.executeAsyncScript(function () { 106 | angular.module('httpMock').clearRequests(); 107 | var callback = arguments[arguments.length - 1]; 108 | callback(true); 109 | }); 110 | }; 111 | 112 | module.exports.add = function(mocks){ 113 | return browser.executeAsyncScript(function () { 114 | angular.module('httpMock').addMocks(arguments[0]); 115 | var callback = arguments[arguments.length - 1]; 116 | callback(true); 117 | }, mocks); 118 | }; 119 | 120 | module.exports.remove = function(mocks){ 121 | return browser.executeAsyncScript(function () { 122 | angular.module('httpMock').removeMocks(arguments[0]); 123 | var callback = arguments[arguments.length - 1]; 124 | callback(JSON.stringify(true)); 125 | }, mocks); 126 | }; 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protractor-http-mock", 3 | "version": "0.10.0", 4 | "description": "Mock HTTP calls in your protractor specs.", 5 | "main": "index.js", 6 | "scripts": { 7 | "example": "node_modules/.bin/grunt example", 8 | "test": "node_modules/.bin/grunt verify", 9 | "client-test": "node_modules/.bin/grunt client-test", 10 | "webdriver-update": "node node_modules/grunt-protractor-runner/scripts/webdriver-manager-update" 11 | }, 12 | "author": "Carlos Atencio", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "grunt": "~1.0.1", 16 | "grunt-browserify": "^5.0.0", 17 | "grunt-contrib-connect": "^1.0.2", 18 | "grunt-contrib-jasmine": "^1.0.3", 19 | "grunt-contrib-jshint": "~1.1.0", 20 | "grunt-jasmine-nodejs": "^1.5.2", 21 | "grunt-protractor-runner": "^4.0.0", 22 | "phantomjs-prebuilt": "^2.1.3", 23 | "protractor-http-mock-sample-plugin": "0.0.1" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/atecarlos/protractor-http-mock" 28 | }, 29 | "dependencies": { 30 | "query-string": "<=4.2.3" 31 | }, 32 | "engines": { 33 | "node": ">=4.0.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /specs/httpMock.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var httpMock = require('../lib/httpMock'); 4 | 5 | describe('http mock', function(){ 6 | it('sets expectations', function(){ 7 | var expectations = [ 8 | { 9 | one: 'mock-one' 10 | }, 11 | { 12 | two: 'mock-two' 13 | } 14 | ]; 15 | 16 | var result = httpMock(expectations); 17 | expect(typeof result).toBe('function'); 18 | expect(result.toString()).toContain('var expectations = [{"one":"mock-one"},{"two":"mock-two"}]'); 19 | }); 20 | 21 | it('sets query string function', function(){ 22 | var expectations = []; 23 | 24 | var result = httpMock(expectations); 25 | expect(result.toString()).toContain('decodeURIComponent'); 26 | }); 27 | }); -------------------------------------------------------------------------------- /specs/initData.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var initDataModule = { 7 | path: '../lib/initData', 8 | require: function () { 9 | return require(this.path); 10 | }, 11 | teardown: function () { 12 | delete require.cache[require.resolve(this.path)]; 13 | } 14 | }; 15 | 16 | var defaultConfig = { 17 | path: path.join(__dirname, '../protractor-conf.js'), 18 | setup: function () { 19 | fs.writeFileSync(this.path, 'exports.config = {}'); 20 | }, 21 | teardown: function () { 22 | fs.unlinkSync(this.path); 23 | } 24 | }; 25 | 26 | var globalMocks = { 27 | setup: function () { 28 | global.protractor = {}; 29 | global.browser = { 30 | addMockModule: function () {} 31 | }; 32 | }, 33 | teardown: function () { 34 | delete global.protractor; 35 | delete global.browser; 36 | } 37 | }; 38 | 39 | 40 | describe('init data', function(){ 41 | beforeEach(function () { 42 | this.initData = initDataModule.require(); 43 | globalMocks.setup(); 44 | defaultConfig.setup(); 45 | }); 46 | afterEach(function () { 47 | initDataModule.teardown(); 48 | globalMocks.teardown(); 49 | defaultConfig.teardown(); 50 | }); 51 | it('will not error when not providing config', function () { 52 | expect(this.initData).not.toThrow(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/convenienceMethods.test.js: -------------------------------------------------------------------------------- 1 | describe('convenience methods', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('get', function(done){ 9 | http.get('test-api.com/user').then(function(response){ 10 | expect(response.data).toBe('pass'); 11 | done(); 12 | }); 13 | }); 14 | 15 | it('post', function(done){ 16 | http.post('/user', { 17 | name: 'Carlos' 18 | }).then(function(response){ 19 | expect(response.data).toBe('Carlos has been saved'); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('head', function(done){ 25 | http.head('/head').then(function(response){ 26 | expect(response.data).toBe('head response'); 27 | done(); 28 | }); 29 | }); 30 | 31 | it('put', function(done){ 32 | http.put('/put').then(function(response){ 33 | expect(response.data).toBe('put response'); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('delete', function(done){ 39 | http.delete('/delete').then(function(response){ 40 | expect(response.data).toBe('delete response'); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('jsonp', function(done){ 46 | http.jsonp('/jsonp').then(function(response){ 47 | expect(response.data).toBe('jsonp response'); 48 | done(); 49 | }); 50 | }); 51 | }); -------------------------------------------------------------------------------- /tests/directCalls.test.js: -------------------------------------------------------------------------------- 1 | describe('direct calls', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | describe('get', function(){ 9 | it('captures a simple get call', function(done){ 10 | http({ 11 | method: 'GET', 12 | url: 'test-api.com/user' 13 | }).then(function(response){ 14 | expect(response.data).toBe('pass'); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('captures get with provided params', function(done){ 20 | http({ 21 | method: 'GET', 22 | url: '/user', 23 | params: { 24 | id: 1 25 | } 26 | }).then(function(response){ 27 | expect(response.data.name).toBe('Carlos Github'); 28 | done(); 29 | }); 30 | }); 31 | 32 | it('treats params get with provided params', function(done){ 33 | http({ 34 | method: 'GET', 35 | url: '/user', 36 | params: { 37 | id: 2 38 | } 39 | }).then(function(response){ 40 | expect(response.data).toBe('pass'); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('treats requests as GET by default', function(done){ 46 | http({ 47 | url: '/user', 48 | params: { 49 | id: 2 50 | } 51 | }).then(function(response){ 52 | expect(response.data).toBe('pass'); 53 | done(); 54 | }); 55 | }); 56 | }); 57 | 58 | describe('post', function(){ 59 | it('captures post calls', function(done){ 60 | http({ 61 | method: 'POST', 62 | url: '/user', 63 | data: { 64 | name: 'Carlos' 65 | } 66 | }).then(function(response){ 67 | expect(response.data).toBe('Carlos has been saved'); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('captures expected post errors', function(done){ 73 | http({ 74 | method: 'POST', 75 | url: '/user', 76 | data: { 77 | name: 'Other name' 78 | } 79 | }).catch(function(response){ 80 | expect(response.status).toBe(500); 81 | expect(response.data.error).toBe('Cant save other users'); 82 | done(); 83 | }); 84 | }); 85 | 86 | it('treats data as optional field', function(done){ 87 | http({ 88 | method: 'POST', 89 | url: '/user', 90 | data: { 91 | some: 'thing' 92 | } 93 | }).then(function(response){ 94 | expect(response.data).toBe('Generic match'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | }); -------------------------------------------------------------------------------- /tests/externalUrl.test.js: -------------------------------------------------------------------------------- 1 | describe('external url', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('should match against external URLs', function(done){ 9 | http({ 10 | method: 'GET', 11 | url: 'https://test-api.com/user' 12 | }).then(function(response){ 13 | expect(response.data).toBe('pass'); 14 | done(); 15 | }); 16 | }); 17 | }); -------------------------------------------------------------------------------- /tests/headers.test.js: -------------------------------------------------------------------------------- 1 | describe('headers', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('matches a request by headers', function(done){ 9 | http({ 10 | method: 'get', 11 | url: 'my-api.com/user', 12 | headers: { 13 | 'x-auth': 'pass', 14 | 'gzip-pro': 'yes' 15 | } 16 | }).then(function(response){ 17 | expect(response.data).toBe('authentication passed'); 18 | done(); 19 | }); 20 | }); 21 | 22 | it('can respond with headers', function(done){ 23 | http({ 24 | url: 'my-api.com/with-headers', 25 | method: 'get' 26 | }).then(function(response){ 27 | expect(response.headers('X-AUTH')).toBe('authenticated'); 28 | expect(response.headers('ANGULAR_API')).toBe('ang=api'); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('can respond with headers in convenience methods', function(done){ 34 | http({ 35 | url: 'my-api.com/with-headers', 36 | method: 'get' 37 | }).success(function(data, status, headers){ 38 | expect(headers('X-AUTH')).toBe('authenticated'); 39 | expect(headers('ANGULAR_API')).toBe('ang=api'); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('ignores header properties when their function return value is null', function(done){ 45 | http({ 46 | method: 'get', 47 | url: 'my-api.com/user', 48 | headers: { 49 | 'x-auth': 'pass', 50 | 'gzip-pro': 'yes', 51 | 'ignore-me': function(){ 52 | return null; 53 | } 54 | } 55 | }).then(function(response){ 56 | expect(response.data).toBe('authentication passed'); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('always returns a header', function(done){ 62 | http({ 63 | method: 'GET', 64 | url: 'test-api.com/user' 65 | }).then(function(response){ 66 | expect(response.headers).toBeDefined(); 67 | expect(response.data).toBe('pass'); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('matches request by complex headers (include functions)', function(done){ 73 | http({ 74 | method: 'get', 75 | url: 'my-api.com/user', 76 | headers: { 77 | 'x-auth': 'pass', 78 | 'gzip-pro': function(config){ 79 | if(config){ 80 | return 'yes'; 81 | } 82 | } 83 | } 84 | }).then(function(response){ 85 | expect(response.data).toBe('authentication passed'); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('response headers function returns all headers if no provided header name', function(done){ 91 | var expectedHeaders = { 92 | 'X-AUTH': 'authenticated', 93 | 'ANGULAR_API': 'ang=api' 94 | }; 95 | 96 | http({ 97 | url: 'my-api.com/with-headers', 98 | method: 'get' 99 | }).then(function(response){ 100 | expect(response.headers()).toEqual(expectedHeaders); 101 | expect(response.headers(null)).toEqual(expectedHeaders); 102 | expect(response.headers(undefined)).toEqual(expectedHeaders); 103 | done(); 104 | }); 105 | }); 106 | }); -------------------------------------------------------------------------------- /tests/interceptors.test.js: -------------------------------------------------------------------------------- 1 | describe('interceptors', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | module.config(['$provide', '$httpProvider', function($provide, $httpProvider){ 9 | 10 | $provide.factory('testInterceptor', function($q) { 11 | return { 12 | request: function(config){ 13 | if(config.url.match(/promise-request/)){ 14 | return $q.when(config); 15 | } 16 | 17 | return config; 18 | }, 19 | 20 | response: function(response){ 21 | var responseReturn; 22 | 23 | if(response.config.url.match(/intercept/)){ 24 | response.data.interceptedResponse = true; 25 | } 26 | 27 | return response; 28 | } 29 | } 30 | }); 31 | 32 | $httpProvider.interceptors.push('testInterceptor'); 33 | 34 | $httpProvider.interceptors.push(function(){ 35 | return { 36 | request: function(config){ 37 | if(config.url.match(/anonymous-intercept/)){ 38 | config.interceptedAnonymousRequest = true; 39 | } 40 | 41 | return config; 42 | } 43 | } 44 | }); 45 | 46 | $httpProvider.interceptors.push(function(){ 47 | var count = 0; 48 | return { 49 | response: function(response){ 50 | if(response.config.url.match(/stateful-intercept/)){ 51 | count++; 52 | response.headers['stateful-anonymous-response-count'] = count; 53 | } 54 | 55 | return response; 56 | } 57 | } 58 | }); 59 | 60 | $httpProvider.interceptors.push(['$q', function($q){ 61 | return { 62 | request: function(config){ 63 | if(config.url.match(/intercept/)){ 64 | config.interceptedRequest = true; 65 | } 66 | 67 | return config; 68 | }, 69 | response: function(response){ 70 | if(response.config.url.match(/promise-response/)){ 71 | response.data.interceptedPromiseResponse = true; 72 | 73 | return $q.when(response); 74 | } 75 | 76 | return response; 77 | }, 78 | responseError: function(response){ 79 | if(response.config.url.match(/response-error/)){ 80 | response.data.interceptedResponseError = true; 81 | } 82 | 83 | return $q.reject(response); 84 | } 85 | } 86 | }]); 87 | 88 | $httpProvider.interceptors.push(['$q', function($q){ 89 | return { 90 | request: function(config){ 91 | if(config.url.match(/with-headers/)){ 92 | config.headers['authorization'] = 'token'; 93 | } 94 | 95 | return config; 96 | }, 97 | response: function(response){ 98 | if(response.config.url.match(/with-headers/)){ 99 | response.headers['response-header'] = 'response-header'; 100 | 101 | return $q.when(response); 102 | } 103 | 104 | return response; 105 | } 106 | } 107 | }]); 108 | }]); 109 | 110 | it('allows intercepts through service factory functions', function(done){ 111 | http({ 112 | method: 'GET', 113 | url: 'test-url.com/intercept' 114 | }).then(function(response){ 115 | expect(response.data.interceptedResponse).toBe(true); 116 | expect(response.data.name).toBe('intercept test'); 117 | done(); 118 | }); 119 | }); 120 | 121 | it('allows for intercepts through anonymous factory', function(done){ 122 | http({ 123 | method: 'GET', 124 | url: 'test-url.com/anonymous-intercept' 125 | }).then(function(response){ 126 | expect(response.data.name).toBe('anonymous intercept test'); 127 | expect(response.data.interceptedResponse).toBeTruthy(); 128 | done(); 129 | }); 130 | }); 131 | 132 | it('allows for intercepts through stateful anonymous factory', function(done){ 133 | http({ 134 | method: 'GET', 135 | url: 'test-url.com/stateful-intercept' 136 | }).then(function(){ 137 | return http({ 138 | method: 'GET', 139 | url: 'test-url.com/stateful-intercept' 140 | }); 141 | }).then(function(response){ 142 | expect(response.headers['stateful-anonymous-response-count']).toBe(2); 143 | done(); 144 | }); 145 | }); 146 | 147 | it('allows for intercepts that return a promise from a request', function(done){ 148 | http({ 149 | method: 'POST', 150 | url: 'test-url.com/promise-request' 151 | }).then(function(response){ 152 | expect(response.data.name).toBe('promise intercept request test'); 153 | done(); 154 | }); 155 | }); 156 | 157 | it('allows for intercepts that return a promise from a response', function(done){ 158 | http({ 159 | method: 'POST', 160 | url: 'test-url.com/promise-response' 161 | }).then(function(response){ 162 | expect(response.data.name).toBe('promise intercept response test'); 163 | expect(response.data.interceptedPromiseResponse).toBeTruthy(); 164 | done(); 165 | }); 166 | }); 167 | 168 | it('allows for intercepts handling an error response', function(done){ 169 | http({ 170 | method: 'GET', 171 | url: 'test-url.com/response-error' 172 | }).catch(function(response){ 173 | expect(response.data.interceptedResponseError).toBe(true); 174 | expect(response.data.name).toBe('intercept test'); 175 | done(); 176 | }); 177 | }); 178 | 179 | it('provides default empty object headers if none are set', function(done){ 180 | http({ 181 | method: 'GET', 182 | url: 'test-url.com/with-headers' 183 | }).then(function(response){ 184 | expect(response.headers['response-header']).toBe('response-header'); 185 | done(); 186 | }); 187 | }); 188 | }); -------------------------------------------------------------------------------- /tests/plugin.test.js: -------------------------------------------------------------------------------- 1 | describe('plugins', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('plugins can match', function(done){ 9 | http({ 10 | method: 'GET', 11 | url: '/plugin', 12 | plugin: {} 13 | }).success(function(data, status){ 14 | expect(data).toBe('plugin match works!'); 15 | expect(status).toBe(200); 16 | done(); 17 | }); 18 | }); 19 | }); -------------------------------------------------------------------------------- /tests/promises.test.js: -------------------------------------------------------------------------------- 1 | describe('$http promises', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('handles success callback', function(done){ 9 | http({ 10 | method: 'GET', 11 | url: 'test-api.com/user' 12 | }).success(function(data, status){ 13 | expect(data).toBe('pass'); 14 | expect(status).toBe(200); 15 | done(); 16 | }); 17 | }); 18 | 19 | it('handles error callback', function(done){ 20 | http.post('test-api.com/user', { 21 | name: 'Other name' 22 | }).error(function(data, status){ 23 | expect(status).toBe(500); 24 | expect(data.error).toBe('Cant save other users'); 25 | done(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /tests/queryString.test.js: -------------------------------------------------------------------------------- 1 | describe('query string', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('matches by query string', function(done){ 9 | http.get('github.com/user/search?id=1&city=ny%26ny').then(function(response){ 10 | expect(response.data.name).toBe('Carlos QS'); 11 | done(); 12 | }); 13 | }); 14 | 15 | it('query string is optional in the mock config', function(done){ 16 | http.get('github.com/user/search?id=2&city=another').then(function(response){ 17 | expect(response.data.name).toBe('Whatever you search'); 18 | done(); 19 | }); 20 | }); 21 | }); -------------------------------------------------------------------------------- /tests/regex.test.js: -------------------------------------------------------------------------------- 1 | describe('regex match', function(){ 2 | var http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('matches any', function(done){ 9 | http.get('/regex/d3ce5994-e662-4223-9968-9fc01694f08f').then(function(response){ 10 | expect(response.data).toBe('regex any match'); 11 | done(); 12 | }); 13 | }); 14 | 15 | it('matches number', function(done){ 16 | http.get('/regex/1').then(function(response){ 17 | expect(response.data).toBe('regex number match'); 18 | done(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/requests.test.js: -------------------------------------------------------------------------------- 1 | describe('requests', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('captures and clears requests', function(done){ 9 | http.head('/head').then(function(){ 10 | expect(module.requests.length).toBeGreaterThan(0); 11 | 12 | var found = false; 13 | 14 | module.requests.forEach(function(request){ 15 | if(request.method === 'HEAD'){ 16 | found = true; 17 | expect(request.url).toBe('/head'); 18 | expect(request.headers).not.toBeDefined(); 19 | } 20 | }); 21 | 22 | expect(found).toBe(true); 23 | 24 | module.clearRequests(); 25 | expect(module.requests.length).toBe(0); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('captures requests with headers', function(done){ 31 | http({ 32 | method: 'get', 33 | url: '/user', 34 | headers: { 35 | 'x-auth': 'pass', 36 | 'gzip-pro': 'yes' 37 | } 38 | }).then(function(){ 39 | expect(module.requests.length).toBeGreaterThan(0); 40 | 41 | var found = false; 42 | 43 | module.requests.forEach(function(request){ 44 | if(request.headers){ 45 | found = true; 46 | expect(request.url).toBe('/user'); 47 | expect(request.headers).toEqual({ 48 | 'x-auth': 'pass', 49 | 'gzip-pro': 'yes' 50 | }); 51 | } 52 | }); 53 | 54 | expect(found).toBe(true); 55 | done(); 56 | }); 57 | }); 58 | }); -------------------------------------------------------------------------------- /tests/runtime.test.js: -------------------------------------------------------------------------------- 1 | describe('runtime mocks', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | var runtimeMocks = [ 9 | { 10 | request: { 11 | method: 'GET', 12 | path: '/runtime' 13 | }, 14 | response: { 15 | data: 'runtime' 16 | } 17 | }, 18 | { 19 | request: { 20 | method: 'GET', 21 | path: '/runtime' 22 | }, 23 | response: { 24 | data: 'override' 25 | } 26 | } 27 | ]; 28 | 29 | afterEach(function(){ 30 | module.removeMocks(runtimeMocks); 31 | }); 32 | 33 | it('adds runtime mocks', function(done){ 34 | module.addMocks(runtimeMocks); 35 | 36 | http({ 37 | method: 'GET', 38 | url: '/runtime' 39 | }).then(function(response){ 40 | expect(response.data).toBe('override'); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('can remove runtime mocks', function(done){ 46 | module.addMocks(runtimeMocks); 47 | module.removeMocks([{ 48 | request: { 49 | method: 'GET', 50 | path: '/runtime' 51 | }, 52 | response: { 53 | data: 'override' 54 | } 55 | }]); 56 | 57 | http({ 58 | method: 'GET', 59 | url: '/runtime' 60 | }).then(function(response){ 61 | expect(response.data).toBe('runtime'); 62 | done(); 63 | }); 64 | }); 65 | }); -------------------------------------------------------------------------------- /tests/setup.js: -------------------------------------------------------------------------------- 1 | (function(context){ 2 | var expectations = [ 3 | { 4 | request: { 5 | method: 'GET', 6 | path: '/user' 7 | }, 8 | response: { 9 | data: 'pass' 10 | } 11 | }, 12 | { 13 | request: { 14 | method: 'GET', 15 | path: '/user', 16 | headers: { 17 | 'x-auth': 'pass', 18 | 'gzip-pro': 'yes' 19 | } 20 | }, 21 | response: { 22 | data: 'authentication passed' 23 | } 24 | }, 25 | { 26 | request: { 27 | method: 'GET', 28 | path: '/user', 29 | params: { 30 | id: 1 31 | } 32 | }, 33 | response: { 34 | data: { 35 | name: 'Carlos Github' 36 | } 37 | } 38 | }, 39 | { 40 | request: { 41 | method: 'GET', 42 | path: '/user/search' 43 | }, 44 | response: { 45 | data: { 46 | name: 'Whatever you search' 47 | } 48 | } 49 | }, 50 | { 51 | request: { 52 | method: 'GET', 53 | path: '/user/search', 54 | queryString: { 55 | id: '1', 56 | city: 'ny&ny' 57 | } 58 | }, 59 | response: { 60 | data: { 61 | name: 'Carlos QS' 62 | } 63 | } 64 | }, 65 | { 66 | request: { 67 | method: 'GET', 68 | path: '/with-headers' 69 | }, 70 | response: { 71 | data: 'read my headers', 72 | headers: { 73 | 'X-AUTH': 'authenticated', 74 | 'ANGULAR_API': 'ang=api' 75 | } 76 | } 77 | }, 78 | { 79 | request: { 80 | method: 'post', 81 | path: '/user' 82 | }, 83 | response: { 84 | status: 200, 85 | data: 'Generic match' 86 | } 87 | }, 88 | { 89 | request: { 90 | method: 'post', 91 | path: '/user', 92 | data: { 93 | name: 'Carlos' 94 | } 95 | }, 96 | response: { 97 | data: 'Carlos has been saved' 98 | } 99 | }, 100 | { 101 | request: { 102 | method: 'post', 103 | path: '/user', 104 | data: { 105 | name: 'Other name' 106 | } 107 | }, 108 | response: { 109 | status: 500, 110 | data: { 111 | error: 'Cant save other users' 112 | } 113 | } 114 | }, 115 | { 116 | request: { 117 | path: '/head', 118 | method: 'head' 119 | }, 120 | response: { 121 | data: 'head response' 122 | } 123 | }, 124 | { 125 | request: { 126 | path: '/delete', 127 | method: 'delete' 128 | }, 129 | response: { 130 | data: 'delete response' 131 | } 132 | }, 133 | { 134 | request: { 135 | path: '/put', 136 | method: 'put' 137 | }, 138 | response: { 139 | data: 'put response' 140 | } 141 | }, 142 | { 143 | request: { 144 | path: '/patch', 145 | method: 'patch' 146 | }, 147 | response: { 148 | data: 'patch response' 149 | } 150 | }, 151 | { 152 | request: { 153 | path: '/jsonp', 154 | method: 'jsonp' 155 | }, 156 | response: { 157 | data: 'jsonp response' 158 | } 159 | }, 160 | { 161 | request: { 162 | path: '/intercept', 163 | interceptedRequest: true, 164 | method: 'get' 165 | }, 166 | response: { 167 | data: { 168 | name: 'intercept test' 169 | } 170 | } 171 | }, 172 | { 173 | request: { 174 | path: '/response-error', 175 | interceptedRequest: true, 176 | method: 'get' 177 | }, 178 | response: { 179 | data: { 180 | name: 'intercept test' 181 | }, 182 | status: 400 183 | } 184 | }, 185 | { 186 | request: { 187 | path: '/anonymous-intercept', 188 | interceptedAnonymousRequest: true, 189 | method: 'get' 190 | }, 191 | response: { 192 | data: { 193 | name: 'anonymous intercept test' 194 | } 195 | } 196 | }, 197 | { 198 | request: { 199 | path: '/stateful-intercept', 200 | method: 'get' 201 | }, 202 | response: { 203 | data: { 204 | name: 'stateful intercept test' 205 | } 206 | } 207 | }, 208 | { 209 | request: { 210 | path: '/promise-request', 211 | method: 'POST' 212 | }, 213 | response: { 214 | data: { 215 | name: 'promise intercept request test' 216 | } 217 | } 218 | }, 219 | { 220 | request: { 221 | path: '/promise-response', 222 | method: 'post' 223 | }, 224 | response: { 225 | data: { 226 | name: 'promise intercept response test' 227 | } 228 | } 229 | }, 230 | { 231 | request: { 232 | path: '/transform-request', 233 | method: 'post', 234 | data: { 235 | name: 'test', 236 | city: 'test city' 237 | } 238 | }, 239 | response: { 240 | data: { 241 | name: 'multiple transforms request test' 242 | } 243 | } 244 | }, 245 | { 246 | request: { 247 | path: '/transform-request', 248 | method: 'post', 249 | data: { 250 | name: 'test' 251 | } 252 | }, 253 | response: { 254 | data: { 255 | name: 'transform request test' 256 | } 257 | } 258 | }, 259 | { 260 | request: { 261 | path: '/transform-response', 262 | method: 'get' 263 | }, 264 | response: { 265 | data: { 266 | name: 'test' 267 | } 268 | } 269 | }, 270 | { 271 | request: { 272 | path: '/plugin', 273 | method: 'get', 274 | plugin: { 275 | check: true 276 | } 277 | }, 278 | response: { 279 | data: 'plugin match works!' 280 | } 281 | }, 282 | { 283 | request: { 284 | path: 'regex\\/.*', 285 | regex: true, 286 | method: 'get', 287 | }, 288 | response: { 289 | data: 'regex any match' 290 | } 291 | }, 292 | { 293 | request: { 294 | path: '\\/regex\\/[0-9]', 295 | regex: true, 296 | method: 'get', 297 | }, 298 | response: { 299 | data: 'regex number match' 300 | } 301 | } 302 | ]; 303 | 304 | var plugins = [ 305 | { 306 | match: function(mockRequest, requestConfig){ 307 | var match = true; 308 | 309 | if(requestConfig.plugin && mockRequest.plugin){ 310 | return mockRequest.plugin.check; 311 | } 312 | 313 | return match; 314 | } 315 | } 316 | ]; 317 | 318 | context.httpMock(expectations, plugins)(); 319 | 320 | context.__module = angular.module('httpMock') 321 | .config(['$qProvider', function ($qProvider) { 322 | $qProvider.errorOnUnhandledRejections(false); 323 | }]) 324 | .run(function($http){ 325 | context.__getHttp = function(){ 326 | return $http; 327 | }; 328 | }); 329 | })(window); -------------------------------------------------------------------------------- /tests/template.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | <% css.forEach(function(style){ %> 8 | 9 | <% }) %> 10 | 11 | 12 | 13 | <% with (scripts) { %> 14 | <% [].concat(polyfills, jasmine, boot, vendor, src, helpers, specs,reporters).forEach(function(script){ %> 15 | 16 | <% }) %> 17 | <% }; %> 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/transforms.test.js: -------------------------------------------------------------------------------- 1 | describe('transforms', function(){ 2 | var module = window.__module, http; 3 | 4 | beforeAll(function(){ 5 | http = window.__getHttp(); 6 | }); 7 | 8 | it('allows multiple transform requests', function(done){ 9 | http.post('test-url.com/transform-request', { 10 | name: 'transform test' 11 | }, { 12 | transformRequest: [function(data){ 13 | data.city = 'test city'; 14 | return data; 15 | }, function(data){ 16 | data.name = 'test'; 17 | return data; 18 | }] 19 | }).then(function(response){ 20 | expect(response.data.name).toBe('multiple transforms request test'); 21 | done(); 22 | }); 23 | }); 24 | 25 | it('allows a single transform requests', function(done){ 26 | http.post('test-url.com/transform-request', { 27 | name: 'transform test' 28 | }, { 29 | transformRequest: function(data){ 30 | data.name = 'test'; 31 | return data; 32 | } 33 | }).then(function(response){ 34 | expect(response.data.name).toBe('transform request test'); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('allows multiple transform responses', function(done){ 40 | http({ 41 | method: 'GET', 42 | url: 'test-url.com/transform-response', 43 | transformResponse: [function (data){ 44 | data.name = 'transform response test'; 45 | return data; 46 | }, function(data){ 47 | data.city = 'test response city'; 48 | return data; 49 | }] 50 | }).then(function(response){ 51 | expect(response.data.name).toBe('transform response test'); 52 | expect(response.data.city).toBe('test response city'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('allows a single transform responses', function(done){ 58 | http({ 59 | method: 'GET', 60 | url: 'test-url.com/transform-response', 61 | transformResponse: function (data){ 62 | data.name = 'transform response test'; 63 | return data; 64 | } 65 | }).then(function(response){ 66 | expect(response.data.name).toBe('transform response test'); 67 | done(); 68 | }); 69 | }); 70 | }); --------------------------------------------------------------------------------