├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── css │ ├── app.css │ └── bootstrap.css ├── index.html └── js │ └── app.js ├── bower.json ├── package.json └── test ├── e2e ├── buffering-spec.js ├── context-passing-spec.js ├── onLoad-spec.js ├── proxy-when-spec.js └── test-harness-spec.js ├── lib └── http-backend-proxy.js ├── protractor-conf.js └── unit ├── buffering-spec.js ├── context-passing-spec.js ├── helpers ├── protractor-browser.js └── regular-expression-scenarios.js ├── onLoad-spec.js ├── shortcut-methods-spec.js ├── sync-context-spec.js └── when-spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | logs/* 2 | !.gitkeep 3 | node_modules/ 4 | bower_components/ 5 | tmp 6 | .DS_Store 7 | .idea 8 | *.sublime* -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "globals": { 4 | "angular": false 5 | } 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.5 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm start > /dev/null & 9 | - npm run update-webdriver 10 | - sleep 1 # give server time to start 11 | 12 | script: 13 | - node_modules/.bin/jasmine-node test/unit/ 14 | - node_modules/.bin/protractor test/protractor-conf.js --browser=firefox 15 | deploy: 16 | provider: npm 17 | email: kenneth@baltrinic.com 18 | api_key: 19 | secure: QslZhL1Mph1Nlgg7EioVyJ3Oyo+GSogC/KfE5FmE7+X2ORgCS6auHsfy3yCzKPj0tT8yFcXaxKZgEwWmfj5P5tSBq7OhIzhQfbmLHZJ/hR3SlgQxV+PJO92/F9h7RBVTMs/vBYepz+lNsnJO0BucYwkFvE/wAFnnb/aPAKf9tmg= 20 | on: 21 | tags: true 22 | repo: kbaltrinic/http-backend-proxy 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Kenneth Baltrinic http://www.baltrinic.com 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # $httpBackend Proxy 2 | 3 | [![Build Status](https://travis-ci.org/kbaltrinic/http-backend-proxy.svg?branch=master)](https://travis-ci.org/kbaltrinic/http-backend-proxy) 4 | 5 | This is a node module for use with the [AngularJS Protractor][protractor] end-to-end testing framework. It allows you to make configuration calls to [$httpBackend][httpBackend] from within Protractor itself as well as share data and functions between the test environment and the browser. Being able to configuring the mock back end along side the tests that depend upon it improves the modularity, encapsulation and flexibility of your tests. The proxy allows $httpBackend to live up to its full potential, making it easier to test angular web applications in abstraction from their back end services. 6 | 7 | ### Credits 8 | 9 | The proxy itself is my own work. However much of the test application and project structure, etc. is based off of [the angular-seed project][angular-seed]. This includes significant parts of this README. 10 | 11 | ### Release Notes and Documentation 12 | 13 | See the [wiki](https://github.com/kbaltrinic/http-backend-proxy/wiki/Release-Notes) for release note. Documentation is in the process of being moved from this readme (which is getting too long) to the wiki. If you are reading this on NPM, I recommend you [look on github](https://github.com/kbaltrinic/http-backend-proxy/blob/master/README.md) for the most recent version. Documentation often gets updated/improved there without a release to NPM. 14 | 15 | The proxy has been tested with the lastest .x versions of the Angular.js from the 1.2, 1.3 and 1.4 series through 1.4.3. It has been tested with the latest .x versions of Protractor from the 0.24, 1.8 and 2.1 series through 2.1.0. 16 | 17 | ## Getting and Using the Proxy 18 | 19 | The proxy is [available via npm](https://www.npmjs.org/package/http-backend-proxy) and can be installed into your project by calling `npm install http-backend-proxy` or better yet, just add `http-backend-proxy` to devDependencies in your `package.json` file. The current version is 1.4.2 and is released under the MIT license. Source code can be found on [GitHub](https://github.com/kbaltrinic/http-backend-proxy). 20 | 21 | To instantiate an instance of the proxy, require it and create a new instance: 22 | ```JavaScript 23 | var HttpBackend = require('http-backend-proxy'); 24 | var proxy = new HttpBackend(browser); 25 | ``` 26 | `browser` is, of course, the protractor browser object. A configuration object may be passed as a second argument to the constructor. Available configuration options are discussed where appropriate below. 27 | 28 | The proxy supports the same basic interface as Angular's $httpBackend so [start with its docs][httpBackend]. Note that all proxied methods return promises in the fashion of most other Protractor methods except when buffering is turned on. (See Buffered Mode below.) Beyond the $httpBackend API, the proxy adds a few additional methods which are discussed in the following sections. 29 | 30 | See the end-to-end tests in `test/e2e` for some examples of usage. 31 | 32 | ### Buffered Mode 33 | 34 | It is possible to use the proxy in a 'buffered' mode by adding `buffer: true` to the configuration object passed to proxy constructor. When buffering is enabled, proxied methods return void and do not immediately pass their calls through to the browser. Calling `flush()` will pass all buffered calls through to the browser at once and return a promise that will be fulfilled after all calls have been executed. 35 | 36 | Buffering is the generally recommended approach because in most cases you will need to make numerous calls to the proxy to set things up the way you want them for a given spec. Buffering will reduce the number of remote calls and considerably speed up your test setup. 37 | 38 | ### Calling Functions and Sharing Data between Tests and Browser 39 | 40 | Using the proxy to configure $httpBackend for simple calls is, well, simple: 41 | ```JavaScript 42 | proxy.whenGET('/logoff').respond(200); 43 | ``` 44 | But what if I want to return some big piece of JSON? If we were coding our test in the browser we might write something like: 45 | ```JavaScript 46 | var searchResults = require('./mock-data/searchResults.json'); 47 | $httpBackend.whenGET('/search&q=beatles').respond(200, searchResults) 48 | ``` 49 | This too will work just fine as long as at the time you call `respond()` on the proxy, searchResults resolves to a JSON object (or anything you would expect to find in a JSON object: strings, numbers, regular expressions, arrays, etc.) 50 | 51 | To get a little fancier, you can even do this: 52 | ```JavaScript 53 | proxy.when('GET', /.*/) 54 | .respond(function(method, url){return [200, 'You called: ' + url];}); 55 | ``` 56 | The proxy will serialize the function and send it up to the browser for execution. 57 | 58 | But what about: 59 | ```JavaScript 60 | var searchResults = require('./mock-data/searchResults.json'); 61 | 62 | function getQueryTermsFrom(url){ ... implementation omitted ... }; 63 | 64 | proxy.when('GET', /search\?q=.*/) 65 | .respond(function(method, url){ 66 | var term = getQueryTermFrom(url); 67 | var fixedUpResponse = searchResults.replace(/__TERM__/, term); 68 | return [200, fixedUpResponse]; 69 | }); 70 | ``` 71 | Now we have a problem. The proxy can serialize the function just fine and send it to the browser, but when it gets there, `getQueryTermFrom` and `searchResults` are not going to resolve. This calls for... 72 | 73 | #### Using the Context Object 74 | 75 | The proxy provides a special object called the context object that is intended to help out in theses kinds of situations. The context object is a property of the proxy, called `context` oddly enough, the value of which will be synchronized between the proxy and the browser-side $httpBackend before any proxied calls are executed on the browser. With this object in place we can now do the following: 76 | 77 | ```JavaScript 78 | var searchResults = require('./mock-data/searchResults.json'); 79 | 80 | proxy.context = { 81 | searchResults: searchResults, 82 | getQueryTermsFrom: function (url){ ... implementation omitted ... }; 83 | } 84 | 85 | proxy.when('GET', /search\?q=.*/) 86 | .respond(function(method, url){ 87 | var term = $httpBackend.context.getQueryTermFrom(url); 88 | var fixedUpResponse = $httpBackend.context.searchResults.replace(/__TERM__/, term); 89 | return [200, fixedUpResponse]; 90 | }); 91 | ``` 92 | 93 | *Hint:* If we rename our in-test proxy from `proxy` to `$httpBackend`, our tests will more easily get through any linting we may have set up. 94 | 95 | Two caveats to the above: First, the context is limited to the following data types: strings, numbers, booleans, null, regular expresions, dates, functions, and arrays and hashes (simple objects) that in turn contain only said types, including further arrays and/or hashes. Second, any functions defined on the context object (`getQueryTermsFrom` in our example) must themselves either be self-contained or only reference other functions and values on the context object (or available globally in the browser, but lets not go there...) 96 | 97 | When buffering is turned off, the context object is synchronized with the browser before every call to `respond()`. When buffering is turned on, this synchronization occurs as part of the call to `flush()`. This is important, because it is the value of the context object at the time of synchronization, not at the point when `respond()` is called on the proxy, that determines its value in the browser. More precisely, since values in the context object may not even be evaluated in the browser until an HTTP call occurs, the value at the time of the HTTP call will be the value of the object as of the most recent prior synchronization. Said another way, there is no closure mechanism in play here. Because of this behavior, it is also possible to manually synchronized the context object at any time. See below for how and why you might want to do this. 98 | 99 | #### Configuring the Context Object 100 | 101 | If for some reason you don't like your context object being called 'context' (or more importantly, if 'context' should turn out to conflict with a future property of $httpBackend), it can be renamed by adding `contextField: "a-new-field-name"` to the configuration object that the proxy is constructed with. You can also disable the auto-synchronization of the context object described above by passing `contextAutoSync: false`. If you do, you will need to manually invoke synchronization. 102 | 103 | #### Manually Synchronizing Contexts 104 | 105 | The state of the context object can be synchronized manually at any time by calling `proxy.syncContext()`. This does not flush the buffer or otherwise send any new configuration to the remote $httpBackend instance. It simply synchronizes the state of the local and in-browser context objects. `syncContext` returns a promise. 106 | 107 | Why do this? Consider the above example where we have a search URL whose return value is essentially defined as context.searchResults. If we can update the context in between tests, we can effectively cause search to return different results for each test. This makes it easy, for example, to test for correct behavior when some, none, and more-than-expected results are returned. 108 | 109 | To avoid having to write this awkward code: 110 | ```JavaScript 111 | proxy.context = { 112 | searchResults: searchResults, 113 | getQueryTermsFrom: function (url){ ... implementation omitted ... }; 114 | } 115 | 116 | proxy.when( ... ); 117 | 118 | ... do some tests ... 119 | 120 | proxy.context.searchResults = emptyResults; 121 | proxy.syncContext(); 122 | 123 | ... do some more tests ... 124 | 125 | //rinse, wash, repeat... 126 | ``` 127 | you can pass the new context directly to syncContext like so: 128 | ```JavaScript 129 | proxy.syncContext({searchResults: emptyResults}); 130 | ``` 131 | The above will *merge* the provided object with any existing context and synchronize the result. Merging allows you to avoid re-specifying the entire context object. Calling `syncContext` in this manner also updates the instance of the context object on the local proxy as well. 132 | 133 | Note that the merge behavior only works if both the current and newly provided values are simple javascript objects. If either value is anything but, then simple assignment is used and the value passed to `syncContext` will completely replace the prior value. Examples of javascript objects that are not 'simple' and will not be merged are arrays, dates, regular expressions and pretty much anything created with the new keyword. Except for arrays and regular expressions, you shouldn't be using most of these anyway as they would never be returned from an HTTP call. The proxy would likely not correctly serialize them either. 134 | 135 | Moreover, merging is only performed for the top-level object. Nested objects are treated atomically and simply replaced. 136 | 137 | If any of this is confusing the [syncContext unit tests](https://github.com/kbaltrinic/http-backend-proxy/blob/master/test/unit/sync-context-spec.js) may help clear it up and give detailed examples of the behavior. 138 | 139 | ### Configuring $httpBackend upon Page Load 140 | 141 | All of the above works great for setting up mock HTTP responses for calls that will be made in response to user interaction with a loaded page. But what about mocking data that your Angular application requests upon page load? The proxy supports doing this through its `onLoad` qualifier. Any configuration of the $httpBackend that needs to happen as part of the Angular applications initialization should be specified as follows: 142 | 143 | ```JavaScript 144 | proxy.onLoad.when(...).respond(...); 145 | ``` 146 | All calls to $httpBackend that are set up in this manner will be buffered and sent to the browser using Protractor's [addMockModule][addMockModule] capability. The proxy hooks into the `browser.get()` method such that when your tests call `get()`, the proxy gathers all calls made to `onLoad...` and wraps them in an Angular module that will execute them in its `run()` callback. The proxy also makes the current value of its context object available within the callback such that it can be reference in the same manner as with post-load calls. 147 | 148 | Calls made using the `onLoad` qualifier are cumulative. For example: 149 | 150 | ```JavaScript 151 | proxy.onLoad.whenGET('/app-config').respond(...); 152 | browser.get('index.html'); 153 | //The /app-config endpoint is configured as part of initializing the index.html page 154 | 155 | ... do some tests... 156 | 157 | proxy.onLoad.whenGET('/user-prefs').respond(...); 158 | browser.get('my-account.html'); 159 | //The /app-config and /user-prefs endpoints are both configured as part of initializing the my-accounts.html page 160 | 161 | ... more tests ... 162 | 163 | ``` 164 | When this is not desired, it is possible to reset the proxy's buffer of commands to send upon page load by calling `proxy.onLoad.reset()`. This also releases the hook into `browser.get()` which the proxy creates when a call is registered using `onLoad`. *To ensure that tests using onLoad do not impact other tests, it is highly recommended that `reset()` be called as part of test clean-up for any tests that use `onLoad`. Further, protractor versions prior to 0.19.0 do not support the `browser.removeMockModule()` method which `reset` uses. Reset will silently fail to remove the module in this case. If you are using a protractor version prior to 0.19.0 you can invoke `browser.clearMockModules()` yourself and deal with the consequences, if any, of having all modules removed. For this reason, use of `onLoad` with earlier versions of Protractor is not recommended.* 165 | 166 | Note that the buffer used for calls against `onLoad` is separate from the buffer for calls made directly against the proxy (when buffering is enabled). Only the former buffer is resettable. 167 | 168 | Again, [looking at the tests](https://github.com/kbaltrinic/http-backend-proxy/blob/master/test/unit/onLoad-spec.js) should help clarify the proxy's `onLoad` behavior. 169 | 170 | ### Resetting the Mock 171 | 172 | The underlying $httpBackend mock does not support resetting the set of configured calls. So there is no way to do this through the proxy either. The simplest solution is to use `browser.get()` to reload your page. This of course resets the entire application state, not just that of the $httpBackend. Doing so may not seem ideal but if used wisely will give you good test isolation as well resetting the proxy. Alternately, you can use the techniques described under context synchronization above to modify the mock's behavior for each test. 173 | 174 | ## The Test-Harness Application 175 | 176 | The angular application that makes up most of this repository is simply a test harness for the proxy. It enables testing the proxy by way of Protractor. However it can also be used to manually tests the 'hard-coded' $httpBackend configuration set up in `app.js`. Manual testing can useful for debugging problems during proxy development. 177 | 178 | Chance are, unless you plan to contribute to the development of http-backend-proxy, you will never need to load the test harness and can safely skip this section. 179 | 180 | ### Setup 181 | 182 | #### Install Dependencies 183 | 184 | There are three kinds of dependencies in this project: tools, angular framework code and bootstrap css. The tools help us manage and test the application. 185 | 186 | * We get the tools we depend upon via `npm`, the [node package manager][npm]. 187 | * We get the angular code via `bower`, a [client-side code package manager][bower]. 188 | * I added `bootstrap.css` as a hard-coded dependency under `app/css` just to make the test harness look nice. :-) Strictly speaking it is not needed. 189 | 190 | None of these dependencies are needed for http-backend-proxy itself. They are only needed for the test harness. Thus the project is configured to only install the npm dependencies if you specify the `--dev` option on your `npm install` command. Likewise, the bower dependencies are installed when you do `npm run` (below) to start the test harness for the first time. 191 | 192 | 193 | Once the node (npm) modules and bower moduels are installed, you should find that you have two new 194 | folders in your project. 195 | 196 | * `node_modules` - contains the npm packages for the tools we need 197 | * `bower_components` - contains the angular framework files 198 | 199 | #### Run the Test Harness 200 | 201 | The project is preconfigured with a simple development web server to host the test harness. The simplest way to start 202 | this server is: 203 | 204 | ``` 205 | npm start 206 | ``` 207 | 208 | Now browse to the app at `http://localhost:8000/app/index.html`. 209 | 210 | 211 | ### Directory Layout 212 | 213 | app/ --> all of the files to be used in production 214 | css/ --> css files 215 | app.css --> default stylesheet 216 | bootstrap.css --> bootstrap stylesheet 217 | index.html --> that test app's single page and view. 218 | js/ --> javascript files 219 | app.js --> the application's angular code 220 | 221 | test/ --> test config and source files 222 | protractor-conf.js --> config file for running e2e tests with Protractor 223 | e2e/ --> end-to-end specs 224 | ... 225 | lib/ 226 | http-backend-proxy.js ==>This is the proxy library itself. 227 | unit/ --> unit level specs/tests 228 | ... 229 | 230 | ## Testing 231 | 232 | There are two kinds of tests in the project: Unit tests and end-to-end tests. 233 | 234 | ### Running the Unit Tests 235 | 236 | The project contains unit tests for testing the proxy behavior in abstraction from Protractor and Selenium. These are written in [Jasmine][jasmine] and run using the [Jasmine-Node][jasmine-node] test runner. 237 | 238 | * the unit tests are found in `test/unit/`. 239 | 240 | The easiest way to run the unit tests is to use the supplied npm script: 241 | 242 | ``` 243 | npm test 244 | ``` 245 | 246 | This script will start the Jasmine-Node test runner to execute the unit tests. 247 | 248 | If you are actually doing development work on the proxy itself (Thank you!) and want to run the tests continuously as you work, use the following command line. 249 | 250 | 251 | ``` 252 | npm run test-watch 253 | ``` 254 | 255 | This will watch both the `test/unit` and `test/lib` directories. 256 | 257 | ### Running the End-to-End Tests 258 | 259 | The project comes with end-to-end tests, again written in [Jasmine][jasmine]. These tests 260 | are run using Protractor. They also demonstrate how to actually use the proxy to manipulate $httpBackend in your tests. I recommend taking a look. 261 | 262 | * the configuration is found at `test/protractor-conf.js` 263 | * the end-to-end tests are found in `test/e2e/` 264 | 265 | To run them, first start the test harness. 266 | 267 | ``` 268 | npm start 269 | ``` 270 | 271 | In addition, since Protractor is built upon WebDriver we need to install this. The angular-seed 272 | project comes with a predefined script to do this: 273 | 274 | ``` 275 | npm run update-webdriver 276 | ``` 277 | 278 | This will download and install the latest version of the stand-alone WebDriver tool. 279 | 280 | Once you have ensured that the development web server hosting the test harness is up and running 281 | and WebDriver is updated, you can run the end-to-end tests using the supplied npm script: 282 | 283 | ``` 284 | npm run e2e 285 | ``` 286 | 287 | This script will execute the end-to-end tests against the application being hosted on the 288 | development server. 289 | 290 | ## Feedback and Support 291 | 292 | To provide feedback or get support, please [post an issue][issues] on the GitHub project. 293 | 294 | [angular-seed]: https://github.com/angular/angular-seed 295 | [httpBackend]: http://docs.angularjs.org/api/ngMockE2E/service/$httpBackend 296 | [addMockModule]: https://github.com/angular/protractor/blob/master/docs/api.md#protractorprototypeaddmockmodule 297 | [bower]: http://bower.io 298 | [npm]: https://www.npmjs.org/ 299 | [node]: http://nodejs.org 300 | [protractor]: https://github.com/angular/protractor 301 | [jasmine]: http://pivotal.github.com/jasmine/ 302 | [jasmine-node]: https://github.com/mhevery/jasmine-node 303 | [http-server]: https://github.com/nodeapps/http-server 304 | [issues]: https://github.com/kbaltrinic/http-backend-proxy/issues -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | .form { 2 | border: solid 1px silver; 3 | margin: 5px; 4 | padding: 5px; 5 | } 6 | 7 | .request{ 8 | position: relative; 9 | padding-top: 25px; 10 | margin-bottom: 25px; 11 | } 12 | .request:after { 13 | content: "Request"; 14 | position: absolute; 15 | top: 3px; 16 | left: 1px; 17 | font-size: 12px; 18 | font-weight: 700; 19 | color: #bbb; 20 | text-transform: uppercase; 21 | letter-spacing: 1px; 22 | } 23 | .response{ 24 | position: relative; 25 | padding-top: 25px; 26 | } 27 | .response:after { 28 | content: "Response"; 29 | position: absolute; 30 | top: 3px; 31 | left: 1px; 32 | font-size: 12px; 33 | font-weight: 700; 34 | color: #bbb; 35 | text-transform: uppercase; 36 | letter-spacing: 1px; 37 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http-backend-proxy Test Harness 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 | {{version}} 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 | 28 |
29 |
30 | 31 |
32 | / 33 | 38 |
39 |
40 |
41 | 42 | 48 |
49 |
50 | 51 | 57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 | 65 | {{response.status}} 66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 |
{{response.data}}
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 |
{{response.headers}}
82 |
83 |
84 |
85 |
86 |
87 | 90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('app', [ 6 | 'ngMockE2E' 7 | ]).value() 8 | .controller('appCtrl', ['$scope', '$http', function($scope, $http) { 9 | 10 | $scope.version = angular.version; 11 | 12 | $scope.prettyPrint || ($scope.prettyPrint = false); 13 | 14 | $scope.request || ($scope.request = { 15 | method: '', 16 | url: '', 17 | headers: '', 18 | data: '' 19 | }); 20 | 21 | var format = $scope.format = function(obj) { 22 | return $scope.prettyPrint ? JSON.stringify(obj, null, 2) : JSON.stringify(obj) 23 | }; 24 | 25 | $scope.$watch('prettyPrint', function() { 26 | if ($scope.response) { 27 | $scope.response = { 28 | data: format($scope.rawResponse.data), 29 | status: $scope.response.status, 30 | headers: format($scope.rawResponse.headers) 31 | }; 32 | } 33 | }); 34 | 35 | if ($scope.$root.initialRequest) { 36 | $scope.request = angular.extend($scope.request, $scope.$root.initialRequest.request); 37 | $scope.$root.$watch('initialRequest.response', function() { 38 | var response = $scope.$root.initialRequest.response; 39 | if (response) { 40 | recordResponse(response.data, response.status, response.headers); 41 | } 42 | }); 43 | } 44 | 45 | $scope.call = function() { 46 | 47 | delete $scope.response; 48 | 49 | var request = angular.copy($scope.request); 50 | 51 | request.url = '/' + request.url; 52 | 53 | var headerRegex = /^\s*([^:\s]+)\s*:\s*(.*)\s*$/mg; 54 | request.headers = {}; 55 | var header; 56 | while (header = headerRegex.exec(request.headers)) { 57 | request.headers[header[1]] = header[2]; 58 | } 59 | 60 | $http(request).success(recordResponse).error(recordResponse); 61 | }; 62 | 63 | function recordResponse(data, status, headers) { 64 | 65 | headers = headers ? headers() : undefined; 66 | $scope.rawResponse = { 67 | data: data, 68 | headers: headers 69 | }; 70 | 71 | $scope.response = { 72 | data: format(data), 73 | status: status, 74 | headers: format(headers) 75 | }; 76 | } 77 | 78 | }]).run(function($httpBackend, $window, $rootScope, $http) { 79 | 80 | $httpBackend.whenGET('/test').respond( 81 | 200, { 82 | msg: "You called /test", 83 | args: null 84 | }, { 85 | 'test-header': 'success' 86 | }); 87 | 88 | $httpBackend.whenGET('/missing').respond( 89 | 404, 90 | "You called /missing", { 91 | 'test-header': 'failed' 92 | }); 93 | 94 | if ($window.location.search.length > 1) { 95 | var request; 96 | try { 97 | request = JSON.parse(decodeURI($window.location.search.substring(1))); 98 | } catch (ex) { 99 | console.log(ex); 100 | } 101 | 102 | if (request) { 103 | $rootScope.initialRequest = { 104 | request: request 105 | }; 106 | 107 | $http(request).success(function(data, status, headers) { 108 | $rootScope.initialRequest.response = { 109 | data: data, 110 | status: status, 111 | headers: headers 112 | }; 113 | }).error(function(data, status, headers) { 114 | $rootScope.initialRequest.response = { 115 | data: data || '', 116 | status: status || 5000, 117 | headers: headers 118 | }; 119 | }); 120 | } 121 | } 122 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-backend-proxy", 3 | "devDependencies": { 4 | "angular": "1.4.3", 5 | "angular-mocks": "1.4.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-backend-proxy", 3 | "private": false, 4 | "version": "1.4.3", 5 | "description": "A proxy that allows configuring the AngularJs e2e $httpBackend service from within protractor based tests and enables protractor-to-browser data sharing.", 6 | "homepage":"https://github.com/kbaltrinic/http-backend-proxy/wiki", 7 | "main": "test/lib/http-backend-proxy.js", 8 | "repository": "https://github.com/kbaltrinic/http-backend-proxy", 9 | "license": "MIT", 10 | "keywords":["angular", "angularjs", "protractor", "e2e", "httpBackend", "testing", "ngMockE2E"], 11 | "author": "Kenneth Baltrinic ", 12 | "bugs": "https://github.com/kbaltrinic/http-backend-proxy/issues", 13 | "devDependencies": { 14 | "jasmine-node": "^1.14.5", 15 | "protractor": "^2.1.0", 16 | "http-server": "^0.8.0", 17 | "bower": "^1.3.1" 18 | }, 19 | "engines": { 20 | "node": ">=0.10.0" 21 | }, 22 | "scripts": { 23 | 24 | "start": "bower install && http-server -p 8000", 25 | "test": "jasmine-node test/unit/", 26 | "test-watch": "jasmine-node --autotest --watch test/lib/ --color test/unit/", 27 | 28 | "update-webdriver": "webdriver-manager update", 29 | "e2e": "protractor test/protractor-conf.js", 30 | "e2e-debug": "protractor debug test/protractor-conf.js" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/e2e/buffering-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * These Specs provide a basic test of the buffering works as expected. 5 | */ 6 | 7 | var HttpBackend = require('../lib/http-backend-proxy'); 8 | 9 | describe('Buffered configuration', function() { 10 | 11 | var proxy, returnValue1, returnValue2; 12 | 13 | var firstRun = true; 14 | 15 | beforeEach(function() { 16 | 17 | if (firstRun) { 18 | firstRun = false; 19 | 20 | browser.get('index.html'); 21 | element(by.id('method')).sendKeys('GET'); 22 | 23 | proxy = new HttpBackend(browser, { buffer: true }); 24 | 25 | returnValue1 = proxy.whenGET('/url1').respond(200); 26 | returnValue2 = proxy.whenGET('/url2').respond(200); 27 | } 28 | 29 | }); 30 | 31 | it('should not return a promise for the first call', function() { 32 | expect(returnValue1).toBeUndefined(); 33 | }); 34 | 35 | it('should not return a promise for the second call', function() { 36 | expect(returnValue2).toBeUndefined(); 37 | }); 38 | 39 | describe('before flushing the buffer', function() { 40 | 41 | it('/url1 should not work', function() { 42 | 43 | element(by.id('url')).clear() 44 | element(by.id('url')).sendKeys('url1'); 45 | element(by.id('call')).click(); 46 | 47 | expect(element(by.id('r-status')).getText()).toEqual(''); 48 | }); 49 | 50 | it('/url2 should not work', function() { 51 | 52 | element(by.id('url')).clear() 53 | element(by.id('url')).sendKeys('url2'); 54 | element(by.id('call')).click(); 55 | 56 | expect(element(by.id('r-status')).getText()).toEqual(''); 57 | }); 58 | }); 59 | 60 | describe('after flushing the buffer', function() { 61 | 62 | var returnValue1; 63 | var firstTime2 = true; 64 | 65 | beforeEach(function() { 66 | 67 | if (firstTime2) { 68 | firstTime2 = false; 69 | 70 | returnValue1 = proxy.flush(); 71 | } 72 | 73 | }); 74 | 75 | it('flush() should return a promise.', function() { 76 | expect(returnValue1 instanceof protractor.promise.Promise).toEqual(true); 77 | }); 78 | 79 | it('/url1 should work', function() { 80 | 81 | element(by.id('url')).clear() 82 | element(by.id('url')).sendKeys('url1'); 83 | element(by.id('call')).click(); 84 | 85 | expect(element(by.id('r-status')).getText()).toEqual('200'); 86 | }); 87 | 88 | it('/url2 should work', function() { 89 | 90 | element(by.id('url')).clear() 91 | element(by.id('url')).sendKeys('url2'); 92 | element(by.id('call')).click(); 93 | 94 | expect(element(by.id('r-status')).getText()).toEqual('200'); 95 | }); 96 | 97 | it('flushing again should not error.', function() { 98 | expect(function() { proxy.flush(); }).not.toThrow(); 99 | }); 100 | 101 | }); 102 | 103 | }); -------------------------------------------------------------------------------- /test/e2e/context-passing-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * These Specs provide a basic test of the buffering works as expected. 5 | */ 6 | 7 | var HttpBackend = require('../lib/http-backend-proxy'); 8 | 9 | describe('Context', function() { 10 | 11 | var $httpBackend; 12 | var firstRun = true; 13 | 14 | beforeEach(function() { 15 | 16 | if (firstRun) { 17 | firstRun = false; 18 | 19 | browser.get('index.html'); 20 | 21 | $httpBackend = new HttpBackend(browser); 22 | 23 | $httpBackend.context = { 24 | statusCode: 205, 25 | statusText: "Sort-of OK ;-)", 26 | date: new Date(2099, 4, 1), 27 | getResponse: function(url) { 28 | return [200, 'You called: ' + url] 29 | } 30 | } 31 | 32 | //So that the test runs correctly in any time zone. 33 | $httpBackend.context.date.setUTCHours(0); 34 | 35 | $httpBackend.when('GET', '/remote') 36 | .respond(function(method, url) { 37 | return [ 38 | $httpBackend.context.statusCode, 39 | $httpBackend.context.statusText 40 | ]; 41 | }); 42 | 43 | $httpBackend.when('GET', '/func') 44 | .respond(function(method, url) { 45 | return $httpBackend.context.getResponse(url); 46 | }); 47 | 48 | $httpBackend.when('GET', '/date') 49 | .respond(function(method, url) { 50 | return [ 51 | 200, 52 | $httpBackend.context.date.toUTCString() 53 | ] 54 | }); 55 | 56 | element(by.id('method')).sendKeys('GET'); 57 | 58 | } 59 | 60 | }); 61 | 62 | it('should be available on the server', function() { 63 | 64 | element(by.id('url')).clear(); 65 | element(by.id('url')).sendKeys('remote'); 66 | element(by.id('call')).click(); 67 | 68 | expect(element(by.id('r-status')).getText()).toEqual('205'); 69 | expect(element(by.id('r-data')).getText()).toEqual('"Sort-of OK ;-)"'); 70 | 71 | }); 72 | 73 | it('should correctly serialize dates', function() { 74 | 75 | element(by.id('url')).clear(); 76 | element(by.id('url')).sendKeys('date'); 77 | element(by.id('call')).click(); 78 | 79 | expect(element(by.id('r-data')).getText()).toEqual('"Fri, 01 May 2099 00:00:00 GMT"'); 80 | 81 | }); 82 | 83 | it('functions should be callable on the server', function() { 84 | 85 | element(by.id('url')).clear(); 86 | element(by.id('url')).sendKeys('func'); 87 | element(by.id('call')).click(); 88 | 89 | expect(element(by.id('r-status')).getText()).toEqual('200'); 90 | expect(element(by.id('r-data')).getText()).toEqual('"You called: /func"'); 91 | 92 | }); 93 | 94 | it('syncronization via syncContext should update the server context', function() { 95 | 96 | $httpBackend.syncContext({ statusText: 'Event Better!' }); 97 | 98 | element(by.id('url')).clear(); 99 | element(by.id('url')).sendKeys('remote'); 100 | element(by.id('call')).click(); 101 | 102 | expect(element(by.id('r-status')).getText()).toEqual('205'); 103 | expect(element(by.id('r-data')).getText()).toEqual('"Event Better!"'); 104 | 105 | }); 106 | 107 | }); -------------------------------------------------------------------------------- /test/e2e/onLoad-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * These Specs provide a basic test of the buffering works as expected. 5 | */ 6 | 7 | var HttpBackend = require('../lib/http-backend-proxy'); 8 | 9 | describe('onLoad', function() { 10 | 11 | var proxy; 12 | 13 | var firstRun = true; 14 | 15 | beforeEach(function() { 16 | 17 | if (firstRun) { 18 | firstRun = false; 19 | 20 | proxy = new HttpBackend(browser, { buffer: true }); 21 | 22 | proxy.context = { 23 | statusCode: 205, 24 | statusText: "chocolate" 25 | }; 26 | 27 | proxy.onLoad.whenGET('user-prefs').respond(function() { 28 | return [$httpBackend.context.statusCode, $httpBackend.context.statusText]; 29 | }); 30 | 31 | browser.get('index.html?{"method":"GET","url":"user-prefs"}'); 32 | } 33 | 34 | }); 35 | 36 | it('should configure the browser early enough to respond to http requests name in module.run() scripts.', function() { 37 | expect(element(by.id('r-status')).getText()).toEqual('205'); 38 | expect(element(by.id('r-data')).getText()).toEqual('"chocolate"'); 39 | }); 40 | 41 | describe('if reset', function() { 42 | 43 | beforeEach(function() { 44 | proxy.onLoad.reset(); 45 | browser.get('index.html?{"method":"GET","url":"user-prefs"}'); 46 | }); 47 | 48 | it('should no longer configure the browser to respond to http requests.', function() { 49 | expect(element(by.id('r-status')).getText()).toEqual('5000'); 50 | }); 51 | }); 52 | 53 | afterEach(function() { 54 | //Always call reset when ever an onload.when... has been invoked. 55 | proxy.onLoad.reset(); 56 | }); 57 | 58 | }); -------------------------------------------------------------------------------- /test/e2e/proxy-when-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * These Specs provide a basic test of tests to assert that the proxy can 5 | * indeed configure $httpBackend from within protractor. 6 | */ 7 | 8 | var HttpBackend = require('../lib/http-backend-proxy'); 9 | 10 | describe('Remote configuration behavior', function() { 11 | 12 | var angularVersion; 13 | var urlSelectorFunctionsSupported; 14 | var firstRun0 = true; 15 | 16 | beforeEach(function() { 17 | 18 | if (firstRun0) { 19 | firstRun0 = false; 20 | 21 | browser.get('index.html'); 22 | element(by.id('version')).getText().then(function(version) { 23 | angularVersion = JSON.parse(version); 24 | urlSelectorFunctionsSupported = angularVersion.major > 1 || angularVersion.minor > 2; 25 | }); 26 | } 27 | 28 | }); 29 | 30 | describe('Basic remotely configured GET call', function() { 31 | 32 | var httpBackend; 33 | var firstRun = true; 34 | 35 | beforeEach(function() { 36 | 37 | if (firstRun) { 38 | firstRun = false; 39 | 40 | browser.get('index.html'); 41 | 42 | httpBackend = new HttpBackend(browser); 43 | httpBackend.when('GET', '/remote').respond(200, { 44 | msg: "You called /remote", 45 | }, { 46 | 'test-header': 'remote success' 47 | }); 48 | 49 | element(by.id('method')).sendKeys('GET'); 50 | element(by.id('url')).sendKeys('remote'); 51 | element(by.id('call')).click(); 52 | 53 | } 54 | 55 | }); 56 | 57 | it('should result in a response status of 200', function() { 58 | expect(element(by.id('r-status')).getText()).toEqual('200'); 59 | }); 60 | 61 | it('should result in the mocked response body being displayed', function() { 62 | expect(element(by.id('r-data')).getText()) 63 | .toEqual('{"msg":"You called /remote"}'); 64 | }); 65 | 66 | it('should result in the mocked response headers being displayed', function() { 67 | expect(element(by.id('r-headers')).getText()) 68 | .toEqual('{"test-header":"remote success"}'); 69 | }); 70 | 71 | }); 72 | 73 | 74 | describe('Remotely configured GET call returning an error', function() { 75 | 76 | var httpBackend; 77 | var firstRun = true; 78 | 79 | beforeEach(function() { 80 | 81 | if (firstRun) { 82 | firstRun = false; 83 | 84 | browser.get('index.html'); 85 | 86 | httpBackend = new HttpBackend(browser); 87 | httpBackend.when('GET', '/not-there').respond(404, 'Not Found.'); 88 | 89 | element(by.id('method')).sendKeys('GET'); 90 | element(by.id('url')).sendKeys('not-there'); 91 | element(by.id('call')).click(); 92 | 93 | } 94 | 95 | }); 96 | 97 | it('should result in a response status of 404', function() { 98 | expect(element(by.id('r-status')).getText()).toEqual('404'); 99 | }); 100 | 101 | it('should result in the mocked response body being displayed', function() { 102 | expect(element(by.id('r-data')).getText()) 103 | .toEqual('"Not Found."'); 104 | }); 105 | 106 | it('should result in the mocked response headers being displayed', function() { 107 | expect(element(by.id('r-headers')).getText()) 108 | .toEqual('{}'); 109 | }); 110 | 111 | }); 112 | 113 | describe('Remotely configured GET call that uses functions', function() { 114 | 115 | var httpBackend; 116 | var firstRun = true; 117 | 118 | beforeEach(function() { 119 | 120 | if (firstRun) { 121 | firstRun = false; 122 | 123 | browser.get('index.html'); 124 | 125 | httpBackend = new HttpBackend(browser); 126 | 127 | var urlSelector = urlSelectorFunctionsSupported 128 | ? function(url) { return url.indexOf('/func') == 0; } 129 | : '/func-test'; 130 | 131 | httpBackend 132 | .when('GET', urlSelector) 133 | .respond(function(method, url) { return [200, 'You called: ' + url]; }); 134 | 135 | element(by.id('method')).sendKeys('GET'); 136 | element(by.id('url')).sendKeys('func-test'); 137 | element(by.id('call')).click(); 138 | 139 | } 140 | 141 | }); 142 | 143 | it('should result in a response status of 200', function() { 144 | expect(element(by.id('r-status')).getText()).toEqual('200'); 145 | }); 146 | 147 | it('should result in the computed response body being displayed', function() { 148 | expect(element(by.id('r-data')).getText()) 149 | .toEqual('"You called: /func-test"'); 150 | }); 151 | 152 | }); 153 | 154 | describe('Remotely configured GET call that uses a reqular expression', function() { 155 | 156 | var httpBackend; 157 | var firstRun = true; 158 | 159 | beforeEach(function() { 160 | 161 | if (firstRun) { 162 | firstRun = false; 163 | 164 | browser.get('index.html'); 165 | 166 | httpBackend = new HttpBackend(browser); 167 | 168 | httpBackend 169 | .when('GET', /regexp/i) 170 | .respond(function(method, url) { return [200, 'You called: ' + url]; }); 171 | 172 | element(by.id('method')).sendKeys('GET'); 173 | element(by.id('url')).sendKeys('get-RegExp'); 174 | element(by.id('call')).click(); 175 | 176 | } 177 | 178 | }); 179 | 180 | it('should result in a response status of 200', function() { 181 | expect(element(by.id('r-status')).getText()).toEqual('200'); 182 | }); 183 | 184 | it('should result in the computed response body being displayed', function() { 185 | expect(element(by.id('r-data')).getText()) 186 | .toEqual('"You called: /get-RegExp"'); 187 | }); 188 | 189 | }); 190 | 191 | }); -------------------------------------------------------------------------------- /test/e2e/test-harness-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * These Specs provide a control set to to prove that the test harness itself is 5 | * working against the 'hard-coded' $httpBackend configuration found in app.sj. 6 | */ 7 | 8 | describe('Default ngMockE2E Behavior', function() { 9 | 10 | describe('Basic GET call', function() { 11 | 12 | var firstRun = true; 13 | 14 | beforeEach(function() { 15 | 16 | if (firstRun) { 17 | firstRun = false; 18 | 19 | browser.get('index.html'); 20 | 21 | element(by.id('method')).sendKeys('GET'); 22 | element(by.id('url')).sendKeys('test'); 23 | element(by.id('call')).click(); 24 | } 25 | 26 | }); 27 | 28 | it('should result in a response status of 200', function() { 29 | expect(element(by.id('r-status')).getText()).toEqual('200'); 30 | }); 31 | 32 | it('should result in the mocked response body being displayed', function() { 33 | expect(element(by.id('r-data')).getText()) 34 | .toEqual('{"msg":"You called /test","args":null}'); 35 | }); 36 | 37 | it('should result in the mocked response headers being displayed', function() { 38 | expect(element(by.id('r-headers')).getText()) 39 | .toEqual('{"test-header":"success"}'); 40 | }); 41 | 42 | }); 43 | 44 | describe('Basic GET call returning an error', function() { 45 | 46 | var firstRun = true; 47 | 48 | beforeEach(function() { 49 | 50 | if (firstRun) { 51 | firstRun = false; 52 | 53 | browser.get('index.html'); 54 | 55 | element(by.id('method')).sendKeys('GET'); 56 | element(by.id('url')).sendKeys('missing'); 57 | element(by.id('call')).click(); 58 | 59 | } 60 | 61 | }); 62 | 63 | it('should result in a response status of 404', function() { 64 | expect(element(by.id('r-status')).getText()).toEqual('404'); 65 | }); 66 | 67 | it('should result in the mocked response body being displayed', function() { 68 | expect(element(by.id('r-data')).getText()) 69 | .toEqual('"You called /missing"'); 70 | }); 71 | 72 | it('should result in the mocked response headers being displayed', function() { 73 | expect(element(by.id('r-headers')).getText()) 74 | .toEqual('{"test-header":"failed"}'); 75 | }); 76 | 77 | }); 78 | 79 | }); -------------------------------------------------------------------------------- /test/lib/http-backend-proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http-backend-proxy - https://github.com/kbaltrinic/http-backend-proxy 3 | * (c) 2014 Kenneth Baltrinic. http://www.baltrinic.com 4 | * License: MIT 5 | */ 6 | 'use strict'; 7 | 8 | var Proxy = function(browser, options) { 9 | 10 | var DEFAULT_CONTEXT_FIELD_NAME = 'context'; 11 | 12 | options || (options = {}); 13 | options.buffer || (options.buffer = false); 14 | 15 | //This is for backward compatibility since we changed the API from 1.1.1 to 1.2 16 | if (options.contextField === false) { 17 | console.warn("Setting contextField: false is deprected. Set contextAutoSync: false instead."); 18 | options.contextAutoSync = false; 19 | } 20 | 21 | options.contextAutoSync = typeof(options.contextAutoSync) === 'undefined' || !!options.contextAutoSync; 22 | options.contextField || (options.contextField = DEFAULT_CONTEXT_FIELD_NAME); 23 | 24 | var proxy = this; 25 | var buffer = []; 26 | 27 | createMethods(this, 'when', buildWhenFunction); 28 | 29 | function createMethods(proxy, prefix, functionBuilder) { 30 | ['', 'GET', 'PUT', 'HEAD', 'POST', 'DELETE', 'PATCH', 'JSONP'].forEach(function(method) { 31 | proxy[prefix + method] = functionBuilder(prefix + method); 32 | }); 33 | } 34 | 35 | function buildWhenFunction(funcName){ 36 | 37 | return function(){ 38 | 39 | var whenJS = '$httpBackend.' + funcName + '(' + stringifyArgs(arguments) + ')'; 40 | 41 | return { 42 | respond: function() { 43 | 44 | var fullJS = whenJS +'.respond(' + stringifyArgs(arguments) + ');' 45 | 46 | return executeOrBuffer(fullJS); 47 | }, 48 | passThrough: function() { 49 | return executeOrBuffer(whenJS +'.passThrough(' + stringifyArgs(arguments) + ');'); 50 | } 51 | }; 52 | 53 | }; 54 | 55 | }; 56 | 57 | var wrapBrowserGet = function(){}; 58 | 59 | function executeOrBuffer(script){ 60 | if(options.buffer){ 61 | buffer.push(script); 62 | if(buffer.length == 1) wrapBrowserGet(); 63 | } else { 64 | script = wrapScriptWithinInjectorInvoke(getContextDefinitionScript() + script); 65 | return browser.executeScript(script); 66 | } 67 | } 68 | 69 | function wrapScriptWithinInjectorInvoke(script){ 70 | return '\ 71 | var el = document.querySelector("' + browser.rootEl + '");\ 72 | angular.element(el).injector().invoke(["$httpBackend", function($httpBackend){\ 73 | ' + script + '}]);'; 74 | } 75 | 76 | if(arguments.length < 3){ 77 | 78 | this[options.contextField] = {}; 79 | 80 | this.flush = function(){ 81 | if(buffer.length > 0){ 82 | var script = wrapScriptWithinInjectorInvoke(getContextDefinitionScript() + buffer.join('\n')); 83 | buffer = []; 84 | return browser.executeScript(script); 85 | } else { 86 | var deferred = protractor.promise.defer(); 87 | deferred.fulfill(); 88 | return deferred.promise; 89 | } 90 | } 91 | 92 | this.syncContext = function(context){ 93 | 94 | if(typeof(context) !== 'undefined'){ 95 | 96 | //If and only if both are simple objects, merge them 97 | if(Object.prototype.toString.call(proxy[options.contextField]) === '[object Object]' 98 | && Object.prototype.toString.call(context) === '[object Object]'){ 99 | 100 | for (var key in context) { 101 | if (context.hasOwnProperty(key)) { 102 | proxy[options.contextField][key] = context[key]; 103 | } 104 | } 105 | 106 | context = proxy[options.contextField]; 107 | 108 | } else { 109 | 110 | proxy[options.contextField] = context; 111 | 112 | } 113 | 114 | } else { 115 | 116 | if (typeof(proxy[options.contextField]) === 'undefined') { 117 | proxy[options.contextField] = {}; 118 | } 119 | 120 | context = proxy[options.contextField]; 121 | } 122 | 123 | return browser.executeScript( 124 | wrapScriptWithinInjectorInvoke( 125 | getContextDefinitionScript(context))); 126 | } 127 | 128 | var onLoad; 129 | this.__defineGetter__("onLoad", function() { 130 | 131 | if (onLoad) return onLoad; 132 | 133 | var _options_ = { 134 | buffer: true, 135 | contextField: options.contextField 136 | }; 137 | return onLoad = new Proxy(browser, _options_, proxy); 138 | 139 | }); 140 | 141 | } else { 142 | 143 | var parent = arguments[2]; 144 | 145 | var buildModuleScript = function() { 146 | var script = getContextDefinitionScript(parent[options.contextField]) + buffer.join('\n'); 147 | return 'angular.module("http-backend-proxy", ["ngMockE2E"]).run(["$httpBackend", function($httpBackend){' + 148 | script.replace(/window\.\$httpBackend/g, '$httpBackend') + '}]);' 149 | } 150 | 151 | wrapBrowserGet = function() { 152 | var get = browser.get; 153 | browser.get = function() { 154 | 155 | if (buffer.length > 0) { 156 | if (browser.get.addedOnce && browser.removeMockModule) { 157 | browser.removeMockModule('http-backend-proxy') 158 | }; 159 | browser.addMockModule('http-backend-proxy', buildModuleScript()); 160 | //addedOnce is a workaround for Protractor issue #764 161 | browser.get.addedOnce = true; 162 | } 163 | 164 | return get.apply(browser, arguments); 165 | }; 166 | 167 | browser.get.__super__ = get; 168 | browser.get.addedOnce = false; 169 | } 170 | 171 | this.reset = function() { 172 | buffer = []; 173 | if (browser.get.__super__) { 174 | if (browser.get.addedOnce && browser.removeMockModule) { 175 | browser.removeMockModule('http-backend-proxy') 176 | }; 177 | browser.get = browser.get.__super__; 178 | } 179 | }; 180 | 181 | } 182 | 183 | function stringifyArgs(args) { 184 | var i, s = []; 185 | for (i = 0; i < args.length; i++) { 186 | s.push(stringifyObject(args[i])); 187 | } 188 | return s.join(', '); 189 | } 190 | 191 | function getContextDefinitionScript(context) { 192 | 193 | if (options.contextAutoSync) { 194 | context = context || proxy[options.contextField]; 195 | } 196 | 197 | if (typeof(context) !== 'undefined') { 198 | return '$httpBackend.' + options.contextField + '=' + stringifyObject(context) + ';'; 199 | } else { 200 | return ''; 201 | } 202 | } 203 | 204 | function stringifyObject(obj) { 205 | 206 | if (obj === null) 207 | return 'null'; 208 | 209 | if (typeof obj === 'function') 210 | return obj.toString(); 211 | 212 | if (obj instanceof Date) 213 | return 'new Date(' + obj.valueOf() + ')'; 214 | 215 | if (obj instanceof RegExp) { 216 | 217 | var regexToString = obj.toString(); 218 | var regexEndIndex = regexToString.lastIndexOf("/"); 219 | var expression = JSON.stringify(regexToString.slice(1, regexEndIndex)); 220 | var modifiers = regexToString.substring(regexEndIndex + 1); 221 | if (modifiers.length > 0) modifiers = "," + JSON.stringify(modifiers); 222 | 223 | return 'new RegExp(' + expression + modifiers + ')'; 224 | } 225 | 226 | if (obj instanceof Array) { 227 | var elements = [] 228 | obj.forEach(function(element) { 229 | elements.push(stringifyObject(element)); 230 | }); 231 | return '[' + elements.join(',') + ']'; 232 | 233 | } else if (typeof(obj) === 'object') { 234 | 235 | var fields = []; 236 | for (var key in obj) { 237 | if (obj.hasOwnProperty(key)) { 238 | fields.push(JSON.stringify(key) + ':' + stringifyObject(obj[key])); 239 | } 240 | } 241 | 242 | return '{' + fields.join(',') + '}'; 243 | } 244 | 245 | return JSON.stringify(obj); 246 | 247 | } 248 | 249 | }; 250 | 251 | module.exports = Proxy; -------------------------------------------------------------------------------- /test/protractor-conf.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | allScriptsTimeout: 11000, 3 | 4 | specs: [ 5 | 'e2e/*.js' 6 | ], 7 | 8 | capabilities: { 9 | 'browserName': 'chrome' 10 | }, 11 | 12 | baseUrl: 'http://localhost:8000/app/', 13 | 14 | framework: 'jasmine', 15 | 16 | jasmineNodeOpts: { 17 | defaultTimeoutInterval: 30000 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/unit/buffering-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | var Browser = require('./helpers/protractor-browser') 5 | 6 | describe('Buffered configuration', function() { 7 | 8 | var browser; 9 | 10 | beforeEach(function() { 11 | 12 | browser = new Browser(); 13 | 14 | }); 15 | 16 | afterEach(function() { 17 | browser.cleanUp(); 18 | }); 19 | 20 | describe('A proxy with buffering not configured', function() { 21 | 22 | var proxy, returnValue1, returnValue2; 23 | 24 | beforeEach(function() { 25 | 26 | proxy = new HttpBackend(browser); 27 | 28 | returnValue1 = proxy.whenGET('/url1').passThrough(); 29 | returnValue2 = proxy.whenGET('/url2').passThrough(); 30 | 31 | }); 32 | 33 | it('should make two executeScript calls', function() { 34 | expect(browser.executeScript.calls.length).toEqual(2); 35 | }); 36 | 37 | it('should call executeScript first for the first call to the proxy', function() { 38 | expect(browser.executeScript.calls[0].args[0]).toContain( 39 | '$httpBackend.whenGET("/url1").passThrough();'); 40 | }); 41 | 42 | it('should call executeScript again for the second call to the proxy', function() { 43 | expect(browser.executeScript.calls[1].args[0]).toContain( 44 | '$httpBackend.whenGET("/url2").passThrough();'); 45 | }); 46 | 47 | it('should pass the data context on each call to the proxy', function() { 48 | expect(browser.executeScript.calls[0].args[0]).toContain( 49 | '$httpBackend.context={};'); 50 | expect(browser.executeScript.calls[1].args[0]).toContain( 51 | '$httpBackend.context={};'); 52 | }); 53 | 54 | it('should return a pending promise for the first call', function() { 55 | expect(returnValue1.isComplete).toEqual(false); 56 | }); 57 | 58 | it('should return a pending promise for the second call', function() { 59 | expect(returnValue2.isComplete).toEqual(false); 60 | }); 61 | 62 | }); 63 | 64 | describe('A proxy with buffering turned off', function() { 65 | 66 | var proxy, returnValue1, returnValue2; 67 | 68 | beforeEach(function() { 69 | 70 | proxy = new HttpBackend(browser, { buffer: false }); 71 | 72 | returnValue1 = proxy.whenGET('/url1').passThrough(); 73 | returnValue2 = proxy.whenGET('/url2').passThrough(); 74 | 75 | }); 76 | 77 | it('should make two executeScript calls', function() { 78 | expect(browser.executeScript.calls.length).toEqual(2); 79 | }); 80 | 81 | it('should call executeScript first for the first call to the proxy', function() { 82 | expect(browser.executeScript.calls[0].args[0]).toContain( 83 | '$httpBackend.whenGET("/url1").passThrough();'); 84 | }); 85 | 86 | it('should call executeScript again for the second call to the proxy', function() { 87 | expect(browser.executeScript.calls[1].args[0]).toContain( 88 | '$httpBackend.whenGET("/url2").passThrough();'); 89 | }); 90 | 91 | it('should pass the data context on each call to the proxy', function() { 92 | expect(browser.executeScript.calls[0].args[0]).toContain( 93 | '$httpBackend.context={};'); 94 | expect(browser.executeScript.calls[1].args[0]).toContain( 95 | '$httpBackend.context={};'); 96 | }); 97 | 98 | it('should return a pending promise for the first call', function() { 99 | expect(returnValue1.isComplete).toEqual(false); 100 | }); 101 | 102 | it('should return a pending promise for the second call', function() { 103 | expect(returnValue2.isComplete).toEqual(false); 104 | }); 105 | 106 | }); 107 | 108 | describe('A proxy with buffering turned on', function() { 109 | 110 | var proxy, returnValue1, returnValue2; 111 | 112 | beforeEach(function() { 113 | 114 | proxy = new HttpBackend(browser, { buffer: true }); 115 | 116 | proxy.context.value = "before"; 117 | 118 | returnValue1 = proxy.whenGET('/url1').passThrough(); 119 | returnValue2 = proxy.whenGET('/url2').passThrough(); 120 | 121 | proxy.context.value = "current"; 122 | 123 | }); 124 | 125 | it('should not return a promise for the first call', function() { 126 | expect(returnValue1).toBeUndefined(); 127 | }); 128 | 129 | it('should not return a promise for the second call', function() { 130 | expect(returnValue2).toBeUndefined(); 131 | }); 132 | 133 | describe('before flushing the buffer', function() { 134 | 135 | it('shoud make no executeScript calls', function() { 136 | expect(browser.executeScript.calls.length).toEqual(0); 137 | }); 138 | 139 | }); 140 | 141 | describe('after flushing the buffer', function() { 142 | 143 | var returnValue1; 144 | 145 | beforeEach(function() { 146 | returnValue1 = proxy.flush(); 147 | }); 148 | 149 | it('flush should return a pending promise.', function() { 150 | expect(returnValue1.isComplete).toEqual(false); 151 | }); 152 | 153 | it('should make one executeScript call', function() { 154 | expect(browser.executeScript.calls.length).toEqual(1); 155 | }); 156 | 157 | it('should include all calls to the proxy in that single call', function() { 158 | expect(browser.executeScript.calls[0].args[0]).toContain( 159 | '$httpBackend.whenGET("/url1").passThrough();\n$httpBackend.whenGET("/url2").passThrough();'); 160 | }); 161 | 162 | it('should pass the current data context to the browser', function() { 163 | expect(browser.executeScript.calls[0].args[0]).toContain( 164 | '$httpBackend.context={"value":"current"};'); 165 | }); 166 | 167 | describe('additional calls to flush', function() { 168 | 169 | var returnValue2; 170 | 171 | beforeEach(function() { 172 | returnValue2 = proxy.flush() 173 | }); 174 | 175 | it('should not call executeScript a second time', function() { 176 | proxy.flush(); 177 | expect(browser.executeScript.calls.length).toEqual(1); 178 | }); 179 | 180 | it('should return a completed promise.', function() { 181 | expect(returnValue2.isComplete).toEqual(true); 182 | }); 183 | 184 | }); 185 | 186 | }); 187 | 188 | }); 189 | 190 | }); -------------------------------------------------------------------------------- /test/unit/context-passing-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | var regexScenarios = require('./helpers/regular-expression-scenarios') 5 | 6 | 7 | describe('The Context Object', function() { 8 | 9 | var browser; 10 | var proxy; 11 | 12 | beforeEach(function() { 13 | 14 | browser = { 15 | executeScript: function() {} 16 | }; 17 | 18 | spyOn(browser, 'executeScript'); 19 | 20 | }); 21 | 22 | describe('when auto-syncronization is disabled', function() { 23 | 24 | beforeEach(function() { 25 | proxy = new HttpBackend(browser, { contextAutoSync: false }); 26 | }); 27 | 28 | it('should still initialize the context object', function() { 29 | expect(proxy.context).toEqual({}); 30 | }); 31 | 32 | it('should not forward any context to the browser even if it exists', function() { 33 | 34 | proxy.context = 'I exist!'; 35 | proxy.whenGET('/someURL').respond(200); 36 | 37 | expect(browser.executeScript.calls[0].args[0]).not.toContain( 38 | '$httpBackend.context='); 39 | 40 | }); 41 | 42 | }); 43 | 44 | describe('when auto-syncronization is disabled the deprecated way', function() { 45 | 46 | beforeEach(function() { 47 | spyOn(console, 'warn'); 48 | proxy = new HttpBackend(browser, { contextField: false }); 49 | }); 50 | 51 | it('should warn the developer', function() { 52 | expect(console.warn).toHaveBeenCalled(); 53 | }); 54 | 55 | it('should still initialize the context object', function() { 56 | expect(proxy.context).toEqual({}); 57 | }); 58 | 59 | it('should still not forward any context to the browser even if it exists', function() { 60 | 61 | proxy.context = 'I exist!'; 62 | proxy.whenGET('/someURL').respond(200); 63 | 64 | expect(browser.executeScript.calls[0].args[0]).not.toContain('$httpBackend.context='); 65 | 66 | }); 67 | 68 | }); 69 | 70 | describe('when configured with an alternate field name', function() { 71 | 72 | beforeEach(function() { 73 | proxy = new HttpBackend(browser, { contextField: 'alternate' }); 74 | }); 75 | 76 | it('should initialize the alternate object', function() { 77 | expect(proxy.alternate).toEqual({}); 78 | }); 79 | 80 | it('should forward the context to the browser under the alternate name', function() { 81 | 82 | proxy.whenGET('/someURL').respond(200); 83 | 84 | expect(browser.executeScript.calls[0].args[0]).toContain( 85 | '$httpBackend.alternate={};$httpBackend.whenGET("/someURL").respond(200);'); 86 | 87 | }); 88 | 89 | }); 90 | 91 | describe('in its default configuration', function() { 92 | 93 | beforeEach(function() { 94 | proxy = new HttpBackend(browser); 95 | }); 96 | 97 | it('should initialize to an empty object', function() { 98 | expect(proxy.context).toEqual({}); 99 | }); 100 | 101 | it('should forwarded to the browser even if empty', function() { 102 | 103 | proxy.whenGET('/someURL').respond(200); 104 | 105 | expect(browser.executeScript.calls[0].args[0]).toContain( 106 | '$httpBackend.context={};$httpBackend.whenGET("/someURL").respond(200);'); 107 | 108 | }); 109 | 110 | it('should forwarded all basic data types', function() { 111 | 112 | proxy.context.string = 'A string'; 113 | proxy.context.number = 1; 114 | proxy.context.boolean = true; 115 | 116 | proxy.whenGET('/someURL').respond(200); 117 | 118 | expect(browser.executeScript.calls[0].args[0]).toContain( 119 | '$httpBackend.context={"string":"A string","number":1,"boolean":true};$httpBackend.whenGET("/someURL").respond(200);'); 120 | 121 | }); 122 | 123 | it('should forwarded arrays', function() { 124 | 125 | proxy.context.array = [1, 2, 3]; 126 | 127 | proxy.whenGET('/someURL').respond(200); 128 | 129 | expect(browser.executeScript.calls[0].args[0]).toContain( 130 | '$httpBackend.context={"array":[1,2,3]};$httpBackend.whenGET("/someURL").respond(200);'); 131 | 132 | }); 133 | 134 | it('should forwarded objects', function() { 135 | 136 | proxy.context.obj = { an: 'object' }; 137 | 138 | proxy.whenGET('/someURL').respond(200); 139 | 140 | expect(browser.executeScript.calls[0].args[0]).toContain( 141 | '$httpBackend.context={"obj":{"an":"object"}};$httpBackend.whenGET("/someURL").respond(200);'); 142 | 143 | }); 144 | 145 | for (var i = 0; i < regexScenarios.length; i++) { 146 | (function(scenario) { 147 | it('should forwarded regular expression ' + scenario.desc, 148 | function() { 149 | 150 | proxy.context.regex = scenario.regex 151 | 152 | proxy.whenGET('/someURL').respond(200); 153 | 154 | expect(browser.executeScript.calls[0].args[0]).toContain( 155 | '$httpBackend.context={"regex":' + scenario.output + 156 | '};$httpBackend.whenGET("/someURL").respond(200);'); 157 | 158 | }); 159 | })(regexScenarios[i]) 160 | } 161 | 162 | it('should forwarded dates', function() { 163 | 164 | proxy.context.date = new Date(1234567890); 165 | 166 | proxy.whenGET('/someURL').respond(200); 167 | 168 | expect(browser.executeScript.calls[0].args[0]).toContain( 169 | '$httpBackend.context={"date":new Date(1234567890)};$httpBackend.whenGET("/someURL").respond(200);'); 170 | 171 | }); 172 | 173 | it('should forwarded functions', function() { 174 | 175 | proxy.context.func = function(n) { return n++; }; 176 | 177 | proxy.whenGET('/someURL').respond(200); 178 | 179 | expect(browser.executeScript.calls[0].args[0]).toContain( 180 | '$httpBackend.context={"func":function (n) { return n++; }};$httpBackend.whenGET("/someURL").respond(200);'); 181 | 182 | }); 183 | 184 | for (var i = 0; i < regexScenarios.length; i++) { 185 | (function(scenario) { 186 | it('should forwarded regular expression ' + scenario.desc + ' when nested in objects', 187 | function() { 188 | 189 | proxy.context.obj = { regex: scenario.regex }; 190 | 191 | proxy.whenGET('/someURL').respond(200); 192 | 193 | expect(browser.executeScript.calls[0].args[0]).toContain( 194 | '$httpBackend.context={"obj":{"regex":' + scenario.output + 195 | '}};$httpBackend.whenGET("/someURL").respond(200);'); 196 | 197 | }); 198 | })(regexScenarios[i]) 199 | } 200 | 201 | it('should forwarded dates when nested in objects', function() { 202 | 203 | proxy.context.obj = { date: new Date(1234567890) }; 204 | 205 | proxy.whenGET('/someURL').respond(200); 206 | 207 | expect(browser.executeScript.calls[0].args[0]).toContain( 208 | '$httpBackend.context={"obj":{"date":new Date(1234567890)}};$httpBackend.whenGET("/someURL").respond(200);'); 209 | 210 | }); 211 | 212 | it('should forwarded functions when nested in objects', function() { 213 | 214 | proxy.context.obj = { 215 | func: function(n) { return n++; } 216 | }; 217 | 218 | proxy.whenGET('/someURL').respond(200); 219 | 220 | expect(browser.executeScript.calls[0].args[0]).toContain( 221 | '$httpBackend.context={"obj":{"func":function (n) { return n++; }}};$httpBackend.whenGET("/someURL").respond(200);'); 222 | 223 | }); 224 | 225 | for (var i = 0; i < regexScenarios.length; i++) { 226 | (function(scenario) { 227 | it('should forwarded regular expression ' + scenario.desc + ' when nested in arrays', 228 | function() { 229 | 230 | proxy.context.array = [scenario.regex]; 231 | 232 | proxy.whenGET('/someURL').respond(200); 233 | 234 | expect(browser.executeScript.calls[0].args[0]).toContain( 235 | '$httpBackend.context={"array":[' + scenario.output + 236 | ']};$httpBackend.whenGET("/someURL").respond(200);'); 237 | 238 | }); 239 | })(regexScenarios[i]) 240 | } 241 | 242 | it('should forwarded dates when nested in arrays', function() { 243 | 244 | proxy.context.array = [new Date(1234567890)]; 245 | 246 | proxy.whenGET('/someURL').respond(200); 247 | 248 | expect(browser.executeScript.calls[0].args[0]).toContain( 249 | '$httpBackend.context={"array":[new Date(1234567890)]};$httpBackend.whenGET("/someURL").respond(200);'); 250 | 251 | }); 252 | 253 | it('should forwarded functions when nested in arrays', function() { 254 | 255 | proxy.context.array = [function(n) { return n++; }]; 256 | 257 | proxy.whenGET('/someURL').respond(200); 258 | 259 | expect(browser.executeScript.calls[0].args[0]).toContain( 260 | '$httpBackend.context={"array":[function (n) { return n++; }]};$httpBackend.whenGET("/someURL").respond(200);'); 261 | 262 | }); 263 | }); 264 | }); -------------------------------------------------------------------------------- /test/unit/helpers/protractor-browser.js: -------------------------------------------------------------------------------- 1 | //Used to mocks the globaly available protractor promise 2 | var protractor = { 3 | promise: { 4 | defer: function() { 5 | var promise = { 6 | isComplete: false 7 | }; 8 | return { 9 | promise: promise, 10 | fulfill: function() { 11 | promise.isComplete = true; 12 | } 13 | }; 14 | } 15 | } 16 | }; 17 | 18 | module.exports = function() { 19 | 20 | GLOBAL.protractor = protractor; 21 | 22 | this.executeScript = function() { 23 | //calling executeScript should return a promise. 24 | var deferred = protractor.promise.defer(); 25 | return deferred.promise; 26 | }; 27 | 28 | spyOn(this, 'executeScript').andCallThrough(); 29 | 30 | this.cleanUp = function() { 31 | delete GLOBAL.protractor; 32 | }; 33 | 34 | }; -------------------------------------------------------------------------------- /test/unit/helpers/regular-expression-scenarios.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | regex : /find me/, 4 | output: 'new RegExp("find me")', 5 | desc : 'litterals' 6 | }, { 7 | regex : /find me/ig, 8 | output: 'new RegExp("find me","gi")', 9 | desc : 'litterals with modifiers' 10 | }, { 11 | regex : new RegExp('find me'), 12 | output: 'new RegExp("find me")', 13 | desc : 'constructors' 14 | }, { 15 | regex : new RegExp('find me', 'ig'), 16 | output: 'new RegExp("find me","gi")', 17 | desc : 'constructors with modifiers' 18 | }, { 19 | regex : /\/a\/path/, 20 | output: 'new RegExp("\\\\/a\\\\/path")', 21 | desc : 'litterals with forward slashes' 22 | }, { 23 | regex : new RegExp('/a/path'), 24 | output: 'new RegExp("\\\\/a\\\\/path")', 25 | desc : 'constructors with forward slashes' 26 | }, { 27 | regex : /'quoted'/, 28 | output: 'new RegExp("\'quoted\'")', 29 | desc : 'litterals with single quotes' 30 | }, { 31 | regex : new RegExp("'quoted'"), 32 | output: 'new RegExp("\'quoted\'")', 33 | desc : 'constructors with single quotes' 34 | }, { 35 | regex : /"quoted"/, 36 | output: 'new RegExp("\\\"quoted\\\"")', 37 | desc : 'litterals with double quotes' 38 | }, { 39 | regex : new RegExp('"quoted"'), 40 | output: 'new RegExp("\\\"quoted\\\"")', 41 | desc : 'constructors with double quotes' 42 | } 43 | ]; -------------------------------------------------------------------------------- /test/unit/onLoad-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | var Browser = require('./helpers/protractor-browser') 5 | 6 | var PROMISE = "PROMISE" 7 | 8 | describe('onLoad configuration', function() { 9 | 10 | var browser, browser_get; 11 | 12 | beforeEach(function() { 13 | 14 | browser = { 15 | get: function() { return PROMISE; }, 16 | addMockModule: function() {}, 17 | removeMockModule: function() {}, 18 | executeScript: function() {} 19 | }; 20 | 21 | spyOn(browser, 'get').andCallThrough(); 22 | spyOn(browser, 'addMockModule'); 23 | spyOn(browser, 'executeScript'); 24 | 25 | //Needed because onLoad will replace browser.get this its own implementation. 26 | //But we need to test that the original still gets called in the end. 27 | browser_get = browser.get; 28 | }); 29 | 30 | describe('The proxy object', function() { 31 | 32 | var proxy; 33 | 34 | beforeEach(function() { 35 | proxy = new HttpBackend(browser); 36 | }); 37 | 38 | it('should not support a reset() function', function() { 39 | expect(proxy.reset).toBeUndefined(); 40 | }); 41 | }); 42 | 43 | describe('The onLoad object', function() { 44 | 45 | var onLoad; 46 | 47 | beforeEach(function() { 48 | var proxy = new HttpBackend(browser); 49 | onLoad = proxy.onLoad 50 | }); 51 | 52 | it('should not have a flush() function', function() { 53 | expect(onLoad.flush).toBeUndefined(); 54 | }); 55 | 56 | it('should not have a syncContext() function', function() { 57 | expect(onLoad.syncContext).toBeUndefined(); 58 | }); 59 | 60 | it('should not have an onLoad object', function() { 61 | expect(onLoad.onLoad).toBeUndefined(); 62 | }); 63 | 64 | it('should not initialize a context object', function() { 65 | expect(onLoad.context).toBeUndefined(); 66 | }); 67 | }); 68 | 69 | describe('A proxy where no onLoad calls were made', function() { 70 | 71 | var proxy, getResult; 72 | 73 | beforeEach(function() { 74 | proxy = new HttpBackend(browser); 75 | var onLoad = proxy.onLoad; 76 | getResult = browser.get('index.html'); 77 | }); 78 | 79 | it('should make no addMockModule calls', function() { 80 | expect(browser.addMockModule).not.toHaveBeenCalled(); 81 | }); 82 | 83 | it('should not patch the browser.get() method', function() { 84 | expect(browser.get).toEqual(browser_get); 85 | }); 86 | 87 | it('should call get once.', function() { 88 | expect(browser_get.calls.length).toEqual(1); 89 | }); 90 | 91 | it('should call get with the passed url.', function() { 92 | expect(browser_get.calls[0].args[0]).toEqual('index.html'); 93 | }); 94 | 95 | it('should return the promise returned by get.', function() { 96 | expect(getResult).toEqual(PROMISE); 97 | }); 98 | 99 | }); 100 | 101 | describe('A proxy where onLoad calls were made', function() { 102 | 103 | var proxy, getResult; 104 | 105 | beforeEach(function() { 106 | proxy = new HttpBackend(browser); 107 | proxy.onLoad.whenGET('/session-info').passThrough(); 108 | proxy.onLoad.whenGET('/preferences').passThrough(); 109 | getResult = browser.get('index.html'); 110 | }); 111 | 112 | it('should call addMockModule once', function() { 113 | expect(browser.addMockModule.calls.length).toEqual(1); 114 | }); 115 | 116 | it('should call addMockModule with a script that adds a module that uses ngMockE2E', function() { 117 | expect(browser.addMockModule.calls[0].args[1]).toContain( 118 | 'angular.module("http-backend-proxy", ["ngMockE2E"]).run(["$httpBackend", function($httpBackend){'); 119 | }); 120 | 121 | it('should call addMockModule with a script that contains all the calls made to onLoad', function() { 122 | expect(browser.addMockModule.calls[0].args[1]).toContain( 123 | '$httpBackend.whenGET("/session-info").passThrough();'); 124 | expect(browser.addMockModule.calls[0].args[1]).toContain( 125 | '$httpBackend.whenGET("/preferences").passThrough();'); 126 | }); 127 | 128 | it('should patch the browser.get() method', function() { 129 | expect(browser.get).not.toEqual(browser_get); 130 | }); 131 | 132 | it('should call get once.', function() { 133 | expect(browser_get.calls.length).toEqual(1); 134 | }); 135 | 136 | it('should call get with the passed url.', function() { 137 | expect(browser_get.calls[0].args[0]).toEqual('index.html'); 138 | }); 139 | 140 | it('should return the promise returned by get.', function() { 141 | expect(getResult).toEqual(PROMISE); 142 | }); 143 | 144 | it('should not call executeScript.', function() { 145 | expect(browser.executeScript).not.toHaveBeenCalled(); 146 | }); 147 | 148 | describe('adding additional calls before subsequent page loads', function() { 149 | 150 | beforeEach(function() { 151 | proxy.onLoad.whenGET('/more-info').passThrough(); 152 | browser.addMockModule.reset(); 153 | browser.get('index.html'); 154 | }); 155 | 156 | it('should include all the previous calls in the onLoad script', function() { 157 | expect(browser.addMockModule.calls[0].args[1]).toContain( 158 | '$httpBackend.whenGET("/session-info").passThrough();'); 159 | expect(browser.addMockModule.calls[0].args[1]).toContain( 160 | '$httpBackend.whenGET("/preferences").passThrough();'); 161 | expect(browser.addMockModule.calls[0].args[1]).toContain( 162 | '$httpBackend.whenGET("/more-info").passThrough();'); 163 | }); 164 | 165 | it('should add the new call to the onLoad script', function() { 166 | expect(browser.addMockModule.calls[0].args[1]).toContain( 167 | '$httpBackend.whenGET("/more-info").passThrough();'); 168 | }); 169 | }); 170 | 171 | describe('calling onLoad.reset()', function() { 172 | 173 | beforeEach(function() { 174 | spyOn(browser, 'removeMockModule'); 175 | proxy.onLoad.reset(); 176 | }); 177 | 178 | it('should remove proxy module from the browser.', function() { 179 | expect(browser.removeMockModule).toHaveBeenCalledWith('http-backend-proxy'); 180 | }); 181 | 182 | it('should remove the browser.get() patch', function() { 183 | expect(browser.get).toEqual(browser_get); 184 | }); 185 | 186 | it('should cause addMockModule to again not be called when browser.get is invoked.', function() { 187 | browser.addMockModule.reset(); 188 | browser.get(); 189 | expect(browser.addMockModule).not.toHaveBeenCalled(); 190 | }); 191 | 192 | }); 193 | 194 | describe('and a context exists', function() { 195 | 196 | beforeEach(function() { 197 | proxy.context = 'proxy context' 198 | browser.addMockModule.reset(); 199 | browser.get('index.html'); 200 | }); 201 | 202 | it('should call addMockModule once', function() { 203 | expect(browser.addMockModule.calls.length).toEqual(1); 204 | }); 205 | 206 | it('should call addMockModule with a script that contains all the calls made to onLoad', function() { 207 | expect(browser.addMockModule.calls[0].args[1]).toContain( 208 | '$httpBackend.whenGET("/session-info").passThrough();'); 209 | expect(browser.addMockModule.calls[0].args[1]).toContain( 210 | '$httpBackend.whenGET("/preferences").passThrough();'); 211 | }); 212 | 213 | it('should call addMockModule with a script that sets the context on $httpBackend', function() { 214 | expect(browser.addMockModule.calls[0].args[1]).toContain( 215 | '$httpBackend.context="proxy context";'); 216 | }); 217 | 218 | describe('applying a context to onLoad', function() { 219 | 220 | beforeEach(function() { 221 | proxy.onLoad.context = 'loader context'; 222 | browser.addMockModule.reset(); 223 | browser.get('index.html'); 224 | }); 225 | 226 | it('should not change the proxy context', function() { 227 | expect(proxy.context).toEqual('proxy context') 228 | }); 229 | 230 | it('should use the proxy context when calling addMockModule', function() { 231 | expect(browser.addMockModule.calls[0].args[1]).toContain( 232 | '$httpBackend.context="proxy context";'); 233 | }); 234 | }); 235 | 236 | describe('changing the proxy context before subsequent page loads', function() { 237 | 238 | beforeEach(function() { 239 | proxy.context = 'new context'; 240 | browser.addMockModule.reset(); 241 | browser.get('index.html'); 242 | }); 243 | 244 | it('should use the new context when calling addMockModule', function() { 245 | expect(browser.addMockModule.calls[0].args[1]).toContain( 246 | '$httpBackend.context="new context";'); 247 | }); 248 | }); 249 | }); 250 | }); 251 | 252 | describe('A proxy with an alternate context field name', function() { 253 | 254 | var proxy; 255 | 256 | beforeEach(function() { 257 | proxy = new HttpBackend(browser, { contextField: 'alternate' }); 258 | proxy.alternate = 'proxy context'; 259 | proxy.onLoad.whenGET(/.*/).passThrough(); 260 | browser.get('index.html'); 261 | }); 262 | 263 | it('should call addMockModule once', function() { 264 | expect(browser.addMockModule.calls.length).toEqual(1); 265 | }); 266 | 267 | it('should call addMockModule with a script that uses the alternate field name to the context on $httpBackend', function() { 268 | expect(browser.addMockModule.calls[0].args[1]).toContain( 269 | '$httpBackend.alternate="proxy context";'); 270 | }); 271 | 272 | }); 273 | 274 | }); -------------------------------------------------------------------------------- /test/unit/shortcut-methods-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | 5 | describe('Shortcut Method JavaScript Generation', function() { 6 | 7 | var browser; 8 | var proxy; 9 | 10 | beforeEach(function() { 11 | 12 | browser = { 13 | executeScript: function() {} 14 | }; 15 | 16 | spyOn(browser, 'executeScript'); 17 | 18 | proxy = new HttpBackend(browser, { contextAutoSync: false }); 19 | 20 | }); 21 | 22 | //GET, HEAD and DELETE all have the same signature so use the same tests 23 | ['GET', 'HEAD', 'DELETE'].forEach(function(method) { 24 | (function(method) { 25 | 26 | describe('the when' + method + ' method', function() { 27 | 28 | var methodName = ['when' + method] 29 | var methodUnderTest; 30 | 31 | beforeEach(function() { 32 | methodUnderTest = proxy[methodName]; 33 | }); 34 | 35 | it('should generate the correct JavaScript when called with a url only.', function() { 36 | methodUnderTest('/url').passThrough(); 37 | expect(browser.executeScript.calls[0].args[0]).toContain( 38 | '$httpBackend.' + methodName + '("/url").passThrough();'); 39 | }); 40 | 41 | it('should generate the correct JavaScript when called with a url function.', function() { 42 | methodUnderTest(function(url) { return url.indexOf('/api') == 0; }).passThrough(); 43 | expect(browser.executeScript.calls[0].args[0]).toContain( 44 | '$httpBackend.' + methodName + '(function (url) { return url.indexOf(\'/api\') == 0; }).passThrough();'); 45 | }); 46 | 47 | it('should generate the correct JavaScript when called with a url regex.', function() { 48 | methodUnderTest(new RegExp('/url')).passThrough(); 49 | expect(browser.executeScript.calls[0].args[0]).toContain( 50 | '$httpBackend.' + methodName + '(new RegExp("\\\\/url")).passThrough();'); 51 | }); 52 | 53 | it('should generate the correct JavaScript when called with a url and headers', function() { 54 | methodUnderTest('/url', { header: 'value' }).passThrough(); 55 | expect(browser.executeScript.calls[0].args[0]).toContain( 56 | '$httpBackend.' + methodName + '("/url", {"header":"value"}).passThrough();'); 57 | }); 58 | 59 | }); 60 | 61 | })(method); 62 | }); 63 | 64 | //POST, PUT and PATCH all have the same signature so use the same tests 65 | ['POST', 'PUT', 'PATCH'].forEach(function(method) { 66 | (function(method) { 67 | 68 | describe('the when' + method + ' method', function() { 69 | 70 | var methodName = ['when' + method] 71 | var methodUnderTest; 72 | 73 | beforeEach(function() { 74 | methodUnderTest = proxy[methodName]; 75 | }); 76 | 77 | it('should generate the correct JavaScript when called with a url only.', function() { 78 | methodUnderTest('/url').passThrough(); 79 | expect(browser.executeScript.calls[0].args[0]).toContain( 80 | '$httpBackend.' + methodName + '("/url").passThrough();'); 81 | }); 82 | 83 | it('should generate the correct JavaScript when called with a url function.', function() { 84 | methodUnderTest(function(url) { return url.indexOf('/api') == 0; }).passThrough(); 85 | expect(browser.executeScript.calls[0].args[0]).toContain( 86 | '$httpBackend.' + methodName + '(function (url) { return url.indexOf(\'/api\') == 0; }).passThrough();'); 87 | }); 88 | 89 | it('should generate the correct JavaScript when called with a url regex.', function() { 90 | methodUnderTest(new RegExp('/url')).passThrough(); 91 | expect(browser.executeScript.calls[0].args[0]).toContain( 92 | '$httpBackend.' + methodName + '(new RegExp("\\\\/url")).passThrough();'); 93 | }); 94 | 95 | it('should generate the correct JavaScript when called with a url and data object.', function() { 96 | methodUnderTest('/url', { key: 'value' }).passThrough(); 97 | expect(browser.executeScript.calls[0].args[0]).toContain( 98 | '$httpBackend.' + methodName + '("/url", {"key":"value"}).passThrough();'); 99 | }); 100 | 101 | it('should generate the correct JavaScript when called with a url and data regex.', function() { 102 | methodUnderTest('/url', new RegExp('pattern')).passThrough(); 103 | expect(browser.executeScript.calls[0].args[0]).toContain( 104 | '$httpBackend.' + methodName + '("/url", new RegExp("pattern")).passThrough();'); 105 | }); 106 | 107 | it('should generate the correct JavaScript when called with a url, data and headers object.', function() { 108 | methodUnderTest('/url', 'string data', { header: 'value' }).passThrough(); 109 | expect(browser.executeScript.calls[0].args[0]).toContain( 110 | '$httpBackend.' + methodName + '("/url", "string data", {"header":"value"}).passThrough();'); 111 | }); 112 | 113 | it('should generate the correct JavaScript when called with a url, data and headers function.', function() { 114 | methodUnderTest('/url', 'string data', function(headers) { return !!headers.my - header; }).passThrough(); 115 | expect(browser.executeScript.calls[0].args[0]).toContain( 116 | '$httpBackend.' + methodName + '("/url", "string data", function (headers) { return !!headers.my - header; }).passThrough();'); 117 | }); 118 | 119 | }); 120 | 121 | })(method); 122 | }) 123 | 124 | describe('the whenJSONP method', function() { 125 | 126 | it('should generate the correct JavaScript when called with a url only.', function() { 127 | proxy.whenJSONP('/url').passThrough(); 128 | expect(browser.executeScript.calls[0].args[0]).toContain( 129 | '$httpBackend.whenJSONP("/url").passThrough();'); 130 | }); 131 | 132 | it('should generate the correct JavaScript when called with a url function.', function() { 133 | proxy.whenJSONP(function(url) { return url.indexOf('/api') == 0; }).passThrough(); 134 | expect(browser.executeScript.calls[0].args[0]).toContain( 135 | '$httpBackend.whenJSONP(function (url) { return url.indexOf(\'/api\') == 0; }).passThrough();'); 136 | }); 137 | 138 | it('should generate the correct JavaScript when called with a url regex.', function() { 139 | proxy.whenJSONP(new RegExp('/url')).passThrough(); 140 | expect(browser.executeScript.calls[0].args[0]).toContain( 141 | '$httpBackend.whenJSONP(new RegExp("\\\\/url")).passThrough();'); 142 | }); 143 | 144 | }); 145 | 146 | }); -------------------------------------------------------------------------------- /test/unit/sync-context-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | var Browser = require('./helpers/protractor-browser') 5 | 6 | describe('The syncContext method', function() { 7 | 8 | var browser; 9 | 10 | beforeEach(function() { 11 | browser = new Browser(); 12 | }); 13 | 14 | afterEach(function() { 15 | browser.cleanUp(); 16 | }); 17 | 18 | describe('when buffering is off', function() { 19 | 20 | var proxy, returnValue; 21 | 22 | beforeEach(function() { 23 | 24 | proxy = new HttpBackend(browser, { buffer: false }); 25 | proxy.context = 'myContext'; 26 | returnValue = proxy.syncContext(); 27 | 28 | }); 29 | 30 | it('should return a pending promise', function() { 31 | expect(returnValue.isComplete).toEqual(false); 32 | }); 33 | 34 | it('should syncronize the context object to the browser', function() { 35 | 36 | expect(browser.executeScript.calls[0].args[0]).toContain( 37 | '$httpBackend.context="myContext";'); 38 | 39 | }); 40 | 41 | it('should not do anything else', function() { 42 | 43 | expect(browser.executeScript.calls.length).toEqual(1); 44 | 45 | }); 46 | }); 47 | 48 | describe('when buffering is on', function() { 49 | 50 | var proxy, returnValue; 51 | 52 | beforeEach(function() { 53 | 54 | proxy = new HttpBackend(browser, { buffer: true }); 55 | proxy.whenGET(/.*/).respond(200); 56 | proxy.context = 'myContext'; 57 | returnValue = proxy.syncContext(); 58 | 59 | }); 60 | 61 | it('should return a pending promise', function() { 62 | expect(returnValue.isComplete).toEqual(false); 63 | }); 64 | 65 | it('should syncronize the context object to the browser', function() { 66 | 67 | expect(browser.executeScript.calls[0].args[0]).toContain( 68 | '$httpBackend.context="myContext";'); 69 | 70 | }); 71 | 72 | it('should not do anything else', function() { 73 | 74 | expect(browser.executeScript.calls.length).toEqual(1); 75 | 76 | }); 77 | 78 | it('especially not flush the buffer', function() { 79 | 80 | browser.executeScript.reset(); 81 | proxy.flush(); 82 | expect(browser.executeScript.calls.length).toEqual(1); 83 | 84 | }); 85 | }); 86 | 87 | describe('when an alternative context field name has been configured', function() { 88 | 89 | var proxy; 90 | 91 | beforeEach(function() { 92 | 93 | proxy = new HttpBackend(browser, { contextField: 'alternative' }); 94 | proxy.alternative = 'alternativeContext'; 95 | proxy.syncContext(); 96 | 97 | }); 98 | 99 | it('should syncronize the alternative context object to the browser', function() { 100 | 101 | expect(browser.executeScript.calls[0].args[0]).toContain( 102 | '$httpBackend.alternative="alternativeContext";'); 103 | 104 | }); 105 | }); 106 | 107 | describe('when auto-syncronization of the context object is disabled', function() { 108 | 109 | var proxy; 110 | 111 | beforeEach(function() { 112 | 113 | proxy = new HttpBackend(browser, { contextAutoSync: false }); 114 | proxy.syncContext(); 115 | 116 | }); 117 | 118 | it('should syncronize the context object to the browser', function() { 119 | 120 | expect(browser.executeScript.calls[0].args[0]).toContain( 121 | '$httpBackend.context={};'); 122 | 123 | }); 124 | 125 | }); 126 | 127 | describe('when not local context object exists', function() { 128 | 129 | var proxy; 130 | 131 | beforeEach(function() { 132 | 133 | proxy = new HttpBackend(browser); 134 | delete proxy.context; 135 | proxy.syncContext(); 136 | 137 | }); 138 | 139 | it('should create the context object on the local proxy', function() { 140 | 141 | expect(proxy.context).toEqual({}); 142 | 143 | }); 144 | 145 | it('should syncronize the context object to the browser', function() { 146 | 147 | expect(browser.executeScript.calls[0].args[0]).toContain( 148 | '$httpBackend.context={};'); 149 | 150 | }); 151 | 152 | }); 153 | 154 | describe('when an explicit context object is provided', function() { 155 | 156 | describe('and both the prior context and the new context are simple objects', function() { 157 | 158 | var proxy; 159 | 160 | beforeEach(function() { 161 | 162 | proxy = new HttpBackend(browser); 163 | proxy.context = { 164 | value1: 'old1', 165 | value2: 'old2' 166 | }; 167 | proxy.syncContext({ 168 | value2: 'new2', 169 | value3: 'new3' 170 | }); 171 | 172 | }); 173 | 174 | it('should syncronize the merged context object to the browser', function() { 175 | 176 | expect(browser.executeScript.calls[0].args[0]).toContain( 177 | '$httpBackend.context={"value1":"old1","value2":"new2","value3":"new3"};'); 178 | 179 | }); 180 | 181 | it('should update the local context object with the merged values', function() { 182 | 183 | expect(proxy.context).toEqual({ 184 | value1: 'old1', 185 | value2: 'new2', 186 | value3: 'new3' 187 | }); 188 | 189 | }); 190 | 191 | }); 192 | 193 | describe('and only the prior context is a simple object', function() { 194 | 195 | var proxy; 196 | 197 | beforeEach(function() { 198 | 199 | proxy = new HttpBackend(browser); 200 | proxy.context = { old: 'value' }; 201 | //A Regex is an object but not something we would want to merge with! 202 | proxy.syncContext(/a regex/); 203 | 204 | }); 205 | 206 | it('should syncronize the new context object to the browser', function() { 207 | 208 | expect(browser.executeScript.calls[0].args[0]).toContain( 209 | '$httpBackend.context=new RegExp("a regex");'); 210 | 211 | }); 212 | 213 | it('should update the local context object with the new value', function() { 214 | 215 | expect(proxy.context).toEqual(/a regex/); 216 | 217 | }); 218 | 219 | }); 220 | 221 | 222 | describe('and only the new context is a simple object', function() { 223 | 224 | var proxy; 225 | 226 | beforeEach(function() { 227 | 228 | proxy = new HttpBackend(browser); 229 | //A array is also an object that we would not want to merge with. 230 | proxy.context = ['old', 'value']; 231 | proxy.syncContext({ aNew: 'value' }); 232 | 233 | }); 234 | 235 | it('should syncronize the new context object to the browser', function() { 236 | 237 | expect(browser.executeScript.calls[0].args[0]).toContain( 238 | '$httpBackend.context={"aNew":"value"};'); 239 | 240 | }); 241 | 242 | it('should update the local context object with the new value', function() { 243 | 244 | expect(proxy.context).toEqual({ aNew: 'value' }); 245 | 246 | }); 247 | 248 | }); 249 | 250 | describe('and an alternative context field name was configured', function() { 251 | 252 | var proxy; 253 | 254 | beforeEach(function() { 255 | 256 | proxy = new HttpBackend(browser, { contextField: 'alternate' }); 257 | proxy.alternate = 'myContext'; 258 | proxy.syncContext('anotherContext'); 259 | 260 | }); 261 | 262 | it('should syncronize the provided context object to the browser', function() { 263 | 264 | expect(browser.executeScript.calls[0].args[0]).toContain( 265 | '$httpBackend.alternate="anotherContext";'); 266 | 267 | }); 268 | 269 | it('should update the local context object', function() { 270 | 271 | expect(proxy.alternate).toEqual('anotherContext'); 272 | 273 | }); 274 | 275 | }); 276 | 277 | describe('and auto-syncronization of the context object has been disabled', function() { 278 | 279 | var proxy; 280 | 281 | beforeEach(function() { 282 | 283 | proxy = new HttpBackend(browser, { contextAutoSync: false }); 284 | proxy.syncContext('anotherContext'); 285 | 286 | }); 287 | 288 | it('should syncronize the provided context object to the browser using the default field name', function() { 289 | 290 | expect(browser.executeScript.calls[0].args[0]).toContain( 291 | '$httpBackend.context="anotherContext";'); 292 | 293 | }); 294 | 295 | it('should update the local context object', function() { 296 | 297 | expect(proxy.context).toEqual('anotherContext'); 298 | 299 | }); 300 | 301 | }); 302 | 303 | }); 304 | 305 | }); -------------------------------------------------------------------------------- /test/unit/when-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpBackend = require('../lib/http-backend-proxy'); 4 | var regexScenarios = require('./helpers/regular-expression-scenarios') 5 | 6 | describe('Proxy.when JavaScript generation', function() { 7 | 8 | var browser; 9 | var proxy; 10 | 11 | beforeEach(function() { 12 | 13 | browser = { executeScript: function() {} }; 14 | spyOn(browser, 'executeScript'); 15 | 16 | proxy = new HttpBackend(browser, { contextAutoSync: false }); 17 | 18 | }); 19 | 20 | it('should generate correct JavaScript for parameterless calls to respond()', function() { 21 | 22 | proxy.when().respond(); 23 | 24 | expect(browser.executeScript.calls[0].args[0]).toContain( 25 | '$httpBackend.when().respond();'); 26 | 27 | }); 28 | 29 | it('should generate correct JavaScript for parameterless calls to passThrough()', function() { 30 | 31 | proxy.when().passThrough(); 32 | 33 | expect(browser.executeScript.calls[0].args[0]).toContain( 34 | '$httpBackend.when().passThrough();'); 35 | 36 | }); 37 | 38 | it('should generate correct JavaScript for calls with one when() and one respond() argument', function() { 39 | 40 | proxy.when('GET').respond(200); 41 | 42 | expect(browser.executeScript.calls[0].args[0]).toContain( 43 | '$httpBackend.when("GET").respond(200);'); 44 | 45 | }); 46 | 47 | it('should generate correct JavaScript for calls with one when() argument to passThrough()', function() { 48 | 49 | proxy.when('GET').passThrough(); 50 | 51 | expect(browser.executeScript.calls[0].args[0]).toContain( 52 | '$httpBackend.when("GET").passThrough();'); 53 | 54 | }); 55 | 56 | it('should generate correct JavaScript for calls with two when() and two respond() arguments', function() { 57 | 58 | proxy.when('GET', '/endpoint').respond(200, { json: 'response' }); 59 | 60 | expect(browser.executeScript.calls[0].args[0]).toContain( 61 | '$httpBackend.when("GET", "/endpoint").respond(200, {"json":"response"});'); 62 | 63 | }); 64 | 65 | it('should generate correct JavaScript for calls with two when() arguments to passThrough()', function() { 66 | 67 | proxy.when('GET', '/endpoint').passThrough(); 68 | 69 | expect(browser.executeScript.calls[0].args[0]).toContain( 70 | '$httpBackend.when("GET", "/endpoint").passThrough();'); 71 | 72 | }); 73 | 74 | it('should generate correct JavaScript for calls with three when() and three respond() arguments', function() { 75 | 76 | proxy 77 | .when('GET', '/endpoint', { json: 'request' }) 78 | .respond(200, { json: 'response' }, { header: 'value' }); 79 | 80 | expect(browser.executeScript.calls[0].args[0]).toContain( 81 | '$httpBackend.when("GET", "/endpoint", {"json":"request"}).respond(200, {"json":"response"}, {"header":"value"});'); 82 | 83 | }); 84 | 85 | it('should generate correct JavaScript for calls with three when() arguments to passThrough()', function() { 86 | 87 | proxy.when('GET', '/endpoint', { json: 'request' }).passThrough(); 88 | 89 | expect(browser.executeScript.calls[0].args[0]).toContain( 90 | '$httpBackend.when("GET", "/endpoint", {"json":"request"}).passThrough();'); 91 | 92 | }); 93 | 94 | it('should generate correct JavaScript for calls with four when() and four respond() arguments', function() { 95 | 96 | proxy 97 | .when('GET', '/endpoint', { json: 'request' }, { header2: "value2" }) 98 | .respond(200, { json: 'response' }, { header: 'value' }, "OK"); 99 | 100 | expect(browser.executeScript.calls[0].args[0]).toContain( 101 | '$httpBackend.when("GET", "/endpoint", {"json":"request"}, {"header2":"value2"}).respond(200, {"json":"response"}, {"header":"value"}, "OK");'); 102 | 103 | }); 104 | 105 | it('should generate correct JavaScript for calls with four when() arguments to passThrough()', function() { 106 | 107 | proxy.when('GET', '/endpoint', { json: 'request' }, { header2: "value2" }).passThrough(); 108 | 109 | expect(browser.executeScript.calls[0].args[0]).toContain( 110 | '$httpBackend.when("GET", "/endpoint", {"json":"request"}, {"header2":"value2"}).passThrough();'); 111 | 112 | }); 113 | 114 | 115 | it('should generate correct JavaScript for calls with complex json arguments', function() { 116 | 117 | //Admittedly in the below, passing a function doesn't make a lot of sense but out aim 118 | //is simply to replicate the local call on the remote browser. It would require special 119 | //handling to NOT serialize functions so well keep it simple. 120 | var json = { 121 | string: "abc", 122 | number: 290, 123 | obj: { nested: "object" }, 124 | array: [232, "ABC", null, {}], 125 | null: null, 126 | regex: /find me/, 127 | func: function() { return null; } 128 | }; 129 | 130 | var stringified = '{"string":"abc","number":290,"obj":{"nested":"object"},"array":[232,"ABC",null,{}],"null":null,"regex":new RegExp("find me"),"func":function () { return null; }}'; 131 | 132 | proxy.when('GET', '/endpoint', json).respond(200, json); 133 | 134 | expect(browser.executeScript.calls[0].args[0]).toContain( 135 | '$httpBackend.when("GET", "/endpoint", ' + stringified + ').respond(200, ' + stringified + ');'); 136 | 137 | }); 138 | 139 | it('should generate correct JavaScript for calls with anonymous functions', function() { 140 | 141 | proxy 142 | .when('GET', function(url) { return url.indexOf('/home') == 0; }) 143 | .respond(function(method, url, data, headers) { return [200, 'you callded ' + url]; }); 144 | 145 | expect(browser.executeScript.calls[0].args[0]).toContain( 146 | '$httpBackend.when("GET", function (url) { return url.indexOf(\'/home\') == 0; }).respond(function (method, url, data, headers) { return [200, \'you callded \' + url]; });'); 147 | 148 | }); 149 | 150 | 151 | for (var i = 0; i < regexScenarios.length; i++) { 152 | (function(scenario) { 153 | it('should generate correct JavaScript for calls with regular expression ' + scenario.desc, 154 | function() { 155 | 156 | proxy.when('GET', scenario.regex).passThrough(); 157 | 158 | expect(browser.executeScript.calls[0].args[0]).toContain( 159 | '$httpBackend.when("GET", ' + scenario.output + ').passThrough();'); 160 | 161 | }); 162 | })(regexScenarios[i]) 163 | } 164 | 165 | }); --------------------------------------------------------------------------------