From 7551b8975a91ee286cc2cf4af5e78f924533575e Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Mon, 7 Sep 2015 13:33:51 +0100 Subject: [PATCH] fix(ngMock/$httpBackend): fail if a url is provided but is `undefined` While the `url` parameter is optional for `$httpBackend.when`, `$httpBackend.expect` and related shortcut methods, it should not have the value of `undefined` if it has been provided. This change ensures that an error is thrown in those cases. Closes #8442 Closes #8462 Closes #10934 Closes #12777 BREAKING CHANGE It is no longer valid to explicitly pass `undefined` as the `url` argument to any of the `$httpBackend.when...()` and `$httpBackend.expect...()` methods. While this argument is optional, it must have a defined value if it is provided. Previously passing an explicit `undefined` value was ignored but this lead to invalid tests passing unexpectedly. --- src/ngMock/angular-mocks.js | 69 ++++++++++++++++-------- test/ngMock/angular-mocksSpec.js | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 23 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index f1a06482e9bc..9fbc7f82eb96 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1423,7 +1423,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1445,6 +1445,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * the `requestHandler` object for possible overrides. */ $httpBackend.when = function(method, url, data, headers, keys) { + + assertArgDefined(arguments, 1, 'url'); + var definition = new MockHttpExpectation(method, url, data, headers, keys), chain = { respond: function(status, data, headers, statusText) { @@ -1472,7 +1475,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1487,7 +1490,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1502,7 +1505,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1517,7 +1520,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1534,7 +1537,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1551,7 +1554,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. * @returns {requestHandler} Returns an object with `respond` method that controls how a matched @@ -1614,7 +1617,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * Creates a new request expectation. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1637,6 +1640,9 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * the `requestHandler` object for possible overrides. */ $httpBackend.expect = function(method, url, data, headers, keys) { + + assertArgDefined(arguments, 1, 'url'); + var expectation = new MockHttpExpectation(method, url, data, headers, keys), chain = { respond: function(status, data, headers, statusText) { @@ -1655,7 +1661,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for GET requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1670,7 +1676,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for HEAD requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1685,7 +1691,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for DELETE requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1700,7 +1706,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for POST requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1718,7 +1724,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for PUT requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1736,7 +1742,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for PATCH requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1754,7 +1760,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for JSONP requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives an url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. * @returns {requestHandler} Returns an object with `respond` method that controls how a matched @@ -1870,18 +1876,35 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { function createShortMethods(prefix) { angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { $httpBackend[prefix + method] = function(url, headers, keys) { + assertArgDefined(arguments, 0, 'url'); + + // Change url to `null` if `undefined` to stop it throwing an exception further down + if (angular.isUndefined(url)) url = null; + return $httpBackend[prefix](method, url, undefined, headers, keys); }; }); angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { $httpBackend[prefix + method] = function(url, data, headers, keys) { + assertArgDefined(arguments, 0, 'url'); + + // Change url to `null` if `undefined` to stop it throwing an exception further down + if (angular.isUndefined(url)) url = null; + return $httpBackend[prefix](method, url, data, headers, keys); }; }); } } +function assertArgDefined(args, index, name) { + if (args.length > index && angular.isUndefined(args[index])) { + throw new Error("Undefined argument `" + name + "`; the argument is provided but not defined"); + } +} + + function MockHttpExpectation(method, url, data, headers, keys) { function getUrlParams(u) { @@ -2462,7 +2485,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header @@ -2494,7 +2517,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2511,7 +2534,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2528,7 +2551,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2545,7 +2568,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2563,7 +2586,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2581,7 +2604,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PATCH requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2599,7 +2622,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on * {@link ngMock.$httpBackend $httpBackend mock}. diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index f7bde4f6b026..04d5493f2659 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1173,6 +1173,97 @@ describe('ngMock', function() { }); + it('should not error if the url is not provided', function() { + expect(function() { + hb.when('GET'); + + hb.whenGET(); + hb.whenPOST(); + hb.whenPUT(); + hb.whenPATCH(); + hb.whenDELETE(); + hb.whenHEAD(); + + hb.expect('GET'); + + hb.expectGET(); + hb.expectPOST(); + hb.expectPUT(); + hb.expectPATCH(); + hb.expectDELETE(); + hb.expectHEAD(); + }).not.toThrow(); + }); + + + it('should error if the url is undefined', function() { + expect(function() { + hb.when('GET', undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenGET(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenDELETE(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenJSONP(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenHEAD(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenPATCH(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenPOST(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.whenPUT(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + + expect(function() { + hb.expect('GET', undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectGET(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectDELETE(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectJSONP(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectHEAD(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectPATCH(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectPOST(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + + expect(function() { + hb.expectPUT(undefined); + }).toThrowError('Undefined argument `url`; the argument is provided but not defined'); + }); + + it('should preserve the order of requests', function() { hb.when('GET', '/url1').respond(200, 'first'); hb.when('GET', '/url2').respond(201, 'second');