From cd28a2e952efbc2f76ff86b6b7d21fd5e41cec65 Mon Sep 17 00:00:00 2001 From: Vojta Jina Date: Tue, 16 Aug 2011 21:24:53 +0200 Subject: feat(mocks.$httpBackend): add $httpBackend mock $httpBackend mock allows: - expecting (asserting) requests - stubbing (responding without asserting) Add empty $httpBackend service (currently just wrapper for $browser.xhr) --- angularFiles.js | 1 + src/AngularPublic.js | 1 + src/angular-mocks.js | 167 +++++++++- src/service/http.js | 13 +- src/service/httpBackend.js | 6 + test/angular-mocksSpec.js | 397 ++++++++++++++++++++++++ test/service/httpSpec.js | 735 ++++++++++++++++++++------------------------- test/widgetsSpec.js | 68 ++--- 8 files changed, 944 insertions(+), 444 deletions(-) create mode 100644 src/service/httpBackend.js diff --git a/angularFiles.js b/angularFiles.js index 6871c2a4..a67aa6cc 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -33,6 +33,7 @@ angularFiles = { 'src/service/sniffer.js', 'src/service/window.js', 'src/service/http.js', + 'src/service/httpBackend.js', 'src/service/locale.js', 'src/directives.js', 'src/markups.js', diff --git a/src/AngularPublic.js b/src/AngularPublic.js index df309189..4d94e901 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -78,6 +78,7 @@ function ngModule($provide, $injector) { $provide.service('$filter', $FilterProvider); $provide.service('$formFactory', $FormFactoryProvider); $provide.service('$http', $HttpProvider); + $provide.service('$httpBackend', $HttpBackendProvider); $provide.service('$location', $LocationProvider); $provide.service('$log', $LogProvider); $provide.service('$parse', $ParseProvider); diff --git a/src/angular-mocks.js b/src/angular-mocks.js index 73bc4cbd..bc0578f5 100644 --- a/src/angular-mocks.js +++ b/src/angular-mocks.js @@ -21,6 +21,7 @@ angular.module.ngMock = function($provide){ $provide.service('$browser', angular.module.ngMock.$BrowserProvider); $provide.service('$exceptionHandler', angular.module.ngMock.$ExceptionHandlerProvider); $provide.service('$log', angular.module.ngMock.$LogProvider); + $provide.service('$httpBackend', angular.module.ngMock.$HttpBackendProvider); }; angular.module.ngMock.$inject = ['$provide']; @@ -38,8 +39,6 @@ angular.module.ngMock.$inject = ['$provide']; * * The following apis can be used in tests: * - * - {@link #xhr} — enables testing of code that uses - * the {@link angular.module.ng.$xhr $xhr service} to make XmlHttpRequests. * - $browser.defer — enables testing of code that uses * {@link angular.module.ng.$defer $defer} for executing functions via the `setTimeout` api. */ @@ -720,6 +719,170 @@ angular.module.ngMock.dump = function(object){ } }; +/** + * @ngdoc object + * @name angular.module.ngMock.$httpBackend + */ +angular.module.ngMock.$HttpBackendProvider = function() { + this.$get = function() { + var definitions = [], + expectations = [], + responses = []; + + function createResponse(status, data, headers) { + return angular.isNumber(status) ? [status, data, headers] : [200, status, data]; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) + throw Error('Expected ' + method + ' ' + url + ' with different data'); + + if (!expectation.matchHeaders(headers)) + throw Error('Expected ' + method + ' ' + url + ' with different headers'); + + expectations.shift(); + + if (expectation.response) { + responses.push(function() { + xhr.$$headers = expectation.response[2]; + callback(expectation.response[0], expectation.response[1]); + }); + return method == 'JSONP' ? undefined : xhr; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (!definition.response) throw Error('No response defined !'); + responses.push(function() { + var response = angular.isFunction(definition.response) ? + definition.response(method, url, data, headers) : definition.response; + xhr.$$headers = response[2]; + callback(response[0], response[1]); + }); + return method == 'JSONP' ? undefined : xhr; + } + } + throw wasExpected ? Error('No response defined !') : + Error('Unexpected request: ' + method + ' ' + url); + } + + $httpBackend.when = function(method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers); + definitions.push(definition); + return { + then: function(status, data, headers) { + definition.response = angular.isFunction(status) ? status : createResponse(status, data, headers); + } + }; + }; + + $httpBackend.expect = function(method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers); + expectations.push(expectation); + return { + respond: function(status, data, headers) { + expectation.response = createResponse(status, data, headers); + } + }; + }; + + $httpBackend.flush = function(count) { + count = count || responses.length; + while (count--) { + if (!responses.length) throw Error('No more pending requests'); + responses.shift()(); + } + }; + + + + $httpBackend.verifyExpectations = function() { + if (expectations.length) { + throw Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + $httpBackend.resetExpectations = function() { + expectations = []; + responses = []; + }; + + return $httpBackend; + }; +}; + +function MockHttpExpectation(method, url, data, headers) { + + this.match = function(m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function(u) { + if (!url) return true; + if (angular.isFunction(url.test)) { + if (!url.test(u)) return false; + } else if (url != u) return false; + + return true; + }; + + this.matchHeaders = function(h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) { + if (!headers(h)) return false; + } else if (!angular.equals(headers, h)) return false; + + return true; + }; + + this.matchData = function(d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) { + if (!data.test(d)) return false; + } else if (data != d) return false; + + return true; + }; + + this.toString = function() { + return method + ' ' + url; + }; +} + +function MockXhr() { + + // hack for testing $http + MockXhr.$$lastInstance = this; + + this.getResponseHeader = function(name) { + return this.$$headers[name]; + }; + + this.getAllResponseHeaders = function() { + var lines = []; + + angular.forEach(this.$$headers, function(value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = noop; +} + window.jstestdriver && (function(window){ /** * Global method to output any number of objects into JSTD console. Useful for debugging. diff --git a/src/service/http.js b/src/service/http.js index 13621f90..087c3809 100644 --- a/src/service/http.js +++ b/src/service/http.js @@ -51,6 +51,7 @@ function transform(data, fns, param) { /** * @ngdoc object * @name angular.module.ng.$http + * @requires $httpBacked * @requires $browser * @requires $exceptionHandler * @requires $cacheFactory @@ -85,8 +86,8 @@ function $HttpProvider() { } }; - this.$get = ['$browser', '$exceptionHandler', '$cacheFactory', '$rootScope', - function($browser, $exceptionHandler, $cacheFactory, $rootScope) { + this.$get = ['$httpBackend', '$browser', '$exceptionHandler', '$cacheFactory', '$rootScope', + function($httpBackend, $browser, $exceptionHandler, $cacheFactory, $rootScope) { var cache = $cacheFactory('$http'), pendingRequestsCount = 0; @@ -235,7 +236,7 @@ function $HttpProvider() { /** * Represents Request object, returned by $http() * - * !!! ACCESS CLOSURE VARS: $browser, $config, $log, $rootScope, cache, pendingRequestsCount + * !!! ACCESS CLOSURE VARS: $httpBackend, $browser, $config, $log, $rootScope, cache, pendingRequestsCount */ function XhrFuture() { var rawRequest, cfg = {}, callbacks = [], @@ -243,7 +244,7 @@ function $HttpProvider() { parsedHeaders; /** - * Callback registered to $browser.xhr: + * Callback registered to $httpBackend(): * - caches the response if desired * - calls fireCallbacks() * - clears the reference to raw request object @@ -265,7 +266,7 @@ function $HttpProvider() { * Fire all registered callbacks for given status code * * This method when: - * - serving response from real request ($browser.xhr callback) + * - serving response from real request * - serving response from cache * * It does: @@ -368,7 +369,7 @@ function $HttpProvider() { fireCallbacks(fromCache[1], fromCache[0]); }); } else { - rawRequest = $browser.xhr(cfg.method, cfg.url, data, done, headers, cfg.timeout); + rawRequest = $httpBackend(cfg.method, cfg.url, data, done, headers, cfg.timeout); } pendingRequestsCount++; diff --git a/src/service/httpBackend.js b/src/service/httpBackend.js new file mode 100644 index 00000000..af3de970 --- /dev/null +++ b/src/service/httpBackend.js @@ -0,0 +1,6 @@ +function $HttpBackendProvider() { + this.$get = ['$browser', function($browser) { + return $browser.xhr; + }]; +} + diff --git a/test/angular-mocksSpec.js b/test/angular-mocksSpec.js index acb019c7..4551d11d 100644 --- a/test/angular-mocksSpec.js +++ b/test/angular-mocksSpec.js @@ -342,4 +342,401 @@ describe('mocks', function() { expect(count).toBe(2); }); }); + + + describe('$httpBackend', function() { + var hb, callback; + + beforeEach(inject(function($httpBackend) { + callback = jasmine.createSpy('callback'); + hb = $httpBackend; + })); + + + it('should respond with first matched definition', function() { + hb.when('GET', '/url1').then(200, 'content', {}); + hb.when('GET', '/url1').then(201, 'another', {}); + + callback.andCallFake(function(status, response) { + expect(status).toBe(200); + expect(response).toBe('content'); + }); + + hb('GET', '/url1', null, callback); + expect(callback).not.toHaveBeenCalled(); + hb.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + + it('should throw error when unexpected request', function() { + hb.when('GET', '/url1').then(200, 'content'); + expect(function() { + hb('GET', '/xxx'); + }).toThrow('Unexpected request: GET /xxx'); + }); + + + it('should match headers if specified', function() { + hb.when('GET', '/url', null, {'X': 'val1'}).then(201, 'content1'); + hb.when('GET', '/url', null, {'X': 'val2'}).then(202, 'content2'); + hb.when('GET', '/url').then(203, 'content3'); + + hb('GET', '/url', null, function(status, response) { + expect(status).toBe(203); + expect(response).toBe('content3'); + }); + + hb('GET', '/url', null, function(status, response) { + expect(status).toBe(201); + expect(response).toBe('content1'); + }, {'X': 'val1'}); + + hb('GET', '/url', null, function(status, response) { + expect(status).toBe(202); + expect(response).toBe('content2'); + }, {'X': 'val2'}); + + hb.flush(); + }); + + + it('should match data if specified', function() { + hb.when('GET', '/a/b', '{a: true}').then(201, 'content1'); + hb.when('GET', '/a/b').then(202, 'content2'); + + hb('GET', '/a/b', '{a: true}', function(status, response) { + expect(status).toBe(201); + expect(response).toBe('content1'); + }); + + hb('GET', '/a/b', null, function(status, response) { + expect(status).toBe(202); + expect(response).toBe('content2'); + }); + + hb.flush(); + }); + + + it('should match only method', function() { + hb.when('GET').then(202, 'c'); + callback.andCallFake(function(status, response) { + expect(status).toBe(202); + expect(response).toBe('c'); + }); + + hb('GET', '/some', null, callback, {}); + hb('GET', '/another', null, callback, {'X-Fake': 'Header'}); + hb('GET', '/third', 'some-data', callback, {}); + hb.flush(); + + expect(callback).toHaveBeenCalled(); + }); + + + it('should expose given headers', function() { + hb.when('GET', '/u1').then(200, null, {'X-Fake': 'Header', 'Content-Type': 'application/json'}); + var xhr = hb('GET', '/u1', null, noop, {}); + hb.flush(); + expect(xhr.getResponseHeader('X-Fake')).toBe('Header'); + expect(xhr.getAllResponseHeaders()).toBe('X-Fake: Header\nContent-Type: application/json'); + }); + + + it('should preserve the order of requests', function() { + hb.when('GET', '/url1').then(200, 'first'); + hb.when('GET', '/url2').then(201, 'second'); + + hb('GET', '/url2', null, callback); + hb('GET', '/url1', null, callback); + + hb.flush(); + + expect(callback.callCount).toBe(2); + expect(callback.argsForCall[0]).toEqual([201, 'second']); + expect(callback.argsForCall[1]).toEqual([200, 'first']); + }); + + + it('then() should take function', function() { + hb.when('GET', '/some').then(function(m, u, d, h) { + return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}]; + }); + + var xhr = hb('GET', '/some', 'data', callback, {a: 'b'}); + hb.flush(); + + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe(301); + expect(callback.mostRecentCall.args[1]).toBe('GET/some;data;a=b'); + expect(xhr.getResponseHeader('Connection')).toBe('keep-alive'); + }); + + + it('expect() should require specified order', function() { + hb.expect('GET', '/url1').respond(200, ''); + hb.expect('GET', '/url2').respond(200, ''); + + expect(function() { + hb('GET', '/url2', null, noop, {}); + }).toThrow('Unexpected request: GET /url2'); + }); + + + it('expect() should have precendence over when()', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(300); + expect(response).toBe('expect'); + }); + + hb.when('GET', '/url').then(200, 'when'); + hb.expect('GET', '/url').respond(300, 'expect'); + + hb('GET', '/url', null, callback, {}); + hb.flush(); + expect(callback).toHaveBeenCalledOnce(); + }); + + + it ('should throw exception when only headers differes from expectation', function() { + hb.when('GET').then(200, '', {}); + hb.expect('GET', '/match', undefined, {'Content-Type': 'application/json'}); + + expect(function() { + hb('GET', '/match', null, noop, {}); + }).toThrow('Expected GET /match with different headers'); + }); + + + it ('should throw exception when only data differes from expectation', function() { + hb.when('GET').then(200, '', {}); + hb.expect('GET', '/match', 'some-data'); + + expect(function() { + hb('GET', '/match', 'different', noop, {}); + }).toThrow('Expected GET /match with different data'); + }); + + + it('expect() should without respond() and use then()', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(201); + expect(response).toBe('data'); + }); + + hb.when('GET', '/some').then(201, 'data'); + hb.expect('GET', '/some'); + hb('GET', '/some', null, callback); + hb.flush(); + + expect(callback).toHaveBeenCalled(); + expect(function() { hb.verifyExpectations(); }).not.toThrow(); + }); + + + it('flush() should not flush requests fired during callbacks', function() { + // regression + hb.when('GET').then(200, ''); + hb('GET', '/some', null, function() { + hb('GET', '/other', null, callback); + }); + + hb.flush(); + expect(callback).not.toHaveBeenCalled(); + }); + + + it('flush() should flush given number of pending requests', function() { + hb.when('GET').then(200, ''); + hb('GET', '/some', null, callback); + hb('GET', '/some', null, callback); + hb('GET', '/some', null, callback); + + hb.flush(2); + expect(callback).toHaveBeenCalled(); + expect(callback.callCount).toBe(2); + }); + + + it('flush() should throw exception when flushing more requests than pending', function() { + hb.when('GET').then(200, ''); + hb('GET', '/url', null, callback); + + expect(function() {hb.flush(2);}).toThrow('No more pending requests'); + expect(callback).toHaveBeenCalledOnce(); + }); + + + it('respond() should set default status 200 if not defined', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(200); + expect(response).toBe('some-data'); + }); + + hb.expect('GET', '/url1').respond('some-data'); + hb.expect('GET', '/url2').respond('some-data', {'X-Header': 'true'}); + hb('GET', '/url1', null, callback); + hb('GET', '/url2', null, callback); + hb.flush(); + expect(callback).toHaveBeenCalled(); + expect(callback.callCount).toBe(2); + }); + + + it('then() should set default status 200 if not defined', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(200); + expect(response).toBe('some-data'); + }); + + hb.when('GET', '/url1').then('some-data'); + hb.when('GET', '/url2').then('some-data', {'X-Header': 'true'}); + hb('GET', '/url1', null, callback); + hb('GET', '/url2', null, callback); + hb.flush(); + expect(callback).toHaveBeenCalled(); + expect(callback.callCount).toBe(2); + }); + + + it('should respond with definition if no response for expectation', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(201); + expect(response).toBe('def-response'); + }); + + hb.when('GET').then(201, 'def-response'); + hb.expect('GET', '/some-url'); + + hb('GET', '/some-url', null, callback); + hb.flush(); + expect(callback).toHaveBeenCalledOnce(); + hb.verifyExpectations(); + }); + + + it('should throw an exception if no response defined', function() { + hb.when('GET', '/test'); + expect(function() { + hb('GET', '/test', null, callback); + }).toThrow('No response defined !'); + }); + + + it('should throw an exception if no response for expection and no definition', function() { + hb.expect('GET', '/url'); + expect(function() { + hb('GET', '/url', null, callback); + }).toThrow('No response defined !'); + }); + + + it('should respond undefined when JSONP method', function() { + hb.when('JSONP', '/url1').then(200); + hb.expect('JSONP', '/url2').respond(200); + + expect(hb('JSONP', '/url1')).toBeUndefined(); + expect(hb('JSONP', '/url2')).toBeUndefined(); + }); + + + describe('verify', function() { + + it('should throw exception if not all expectations were satisfied', function() { + hb.expect('POST', '/u1', 'ddd').respond(201, '', {}); + hb.expect('GET', '/u2').respond(200, '', {}); + hb.expect('POST', '/u3').respond(201, '', {}); + + hb('POST', '/u1', 'ddd', noop, {}); + + expect(function() {hb.verifyExpectations();}) + .toThrow('Unsatisfied requests: GET /u2, POST /u3'); + }); + + + it('should do nothing when no expectation', function() { + hb.when('DELETE', '/some').then(200, ''); + + expect(function() {hb.verifyExpectations();}).not.toThrow(); + }); + + + it('should do nothing when all expectations satisfied', function() { + hb.expect('GET', '/u2').respond(200, '', {}); + hb.expect('POST', '/u3').respond(201, '', {}); + hb.when('DELETE', '/some').then(200, ''); + + hb('GET', '/u2', noop); + hb('POST', '/u3', noop); + + expect(function() {hb.verifyExpectations();}).not.toThrow(); + }); + }); + + + describe('reset', function() { + + it('should remove all expectations', function() { + hb.expect('GET', '/u2').respond(200, '', {}); + hb.expect('POST', '/u3').respond(201, '', {}); + hb.resetExpectations(); + + expect(function() {hb.verifyExpectations();}).not.toThrow(); + }); + + + it('should remove all responses', function() { + hb.expect('GET', '/url').respond(200, '', {}); + hb('GET', '/url', null, callback, {}); + hb.resetExpectations(); + hb.flush(); + + expect(callback).not.toHaveBeenCalled(); + }); + }); + + + describe('MockHttpExpectation', function() { + + it('should accept url as regexp', function() { + var exp = new MockHttpExpectation('GET', /^\/x/); + + expect(exp.match('GET', '/x')).toBe(true); + expect(exp.match('GET', '/xxx/x')).toBe(true); + expect(exp.match('GET', 'x')).toBe(false); + expect(exp.match('GET', 'a/x')).toBe(false); + }); + + + it('should accept data as regexp', function() { + var exp = new MockHttpExpectation('POST', '/url', /\{.*?\}/); + + expect(exp.match('POST', '/url', '{"a": "aa"}')).toBe(true); + expect(exp.match('POST', '/url', '{"one": "two"}')).toBe(true); + expect(exp.match('POST', '/url', '{"one"')).toBe(false); + }); + + + it('should ignore data only if undefined (not null or false)', function() { + var exp = new MockHttpExpectation('POST', '/url', null); + expect(exp.matchData(null)).toBe(true); + expect(exp.matchData('some-data')).toBe(false); + + exp = new MockHttpExpectation('POST', '/url', undefined); + expect(exp.matchData(null)).toBe(true); + expect(exp.matchData('some-data')).toBe(true); + }); + + + it('should accept headers as function', function() { + var exp = new MockHttpExpectation('GET', '/url', undefined, function(h) { + return h['Content-Type'] == 'application/json'; + }); + + expect(exp.matchHeaders({})).toBe(false); + expect(exp.matchHeaders({'Content-Type': 'application/json', 'X-Another': 'true'})).toBe(true); + }); + }); + }); }); diff --git a/test/service/httpSpec.js b/test/service/httpSpec.js index 196a57ed..75e85359 100644 --- a/test/service/httpSpec.js +++ b/test/service/httpSpec.js @@ -3,98 +3,68 @@ // TODO(vojta): refactor these tests to use new inject() syntax describe('$http', function() { - var $http, $browser, $exceptionHandler, // services - method, url, data, headers, timeout, // passed arguments - onSuccess, onError, // callback spies - scope, errorLogs, respond, rawXhrObject, future; + var $http, $browser, $exceptionHandler, $httpBackend, + scope, callback, future, callback; beforeEach(inject(function($injector) { $injector.get('$exceptionHandlerProvider').mode('log'); scope = $injector.get('$rootScope'); $http = $injector.get('$http'); $browser = $injector.get('$browser'); + $httpBackend = $injector.get('$httpBackend'); $exceptionHandler = $injector.get('$exceptionHandler'); - - // TODO(vojta): move this into mock browser ? - respond = method = url = data = headers = null; - rawXhrObject = { - abort: jasmine.createSpy('request.abort'), - getResponseHeader: function(h) {return h + '-val';}, - getAllResponseHeaders: function() { - return 'content-encoding: gzip\nserver: Apache\n'; - } - }; - spyOn(scope, '$apply'); - spyOn($browser, 'xhr').andCallFake(function(m, u, d, c, h, t) { - method = m; - url = u; - data = d; - respond = c; - headers = h; - timeout = t; - return rawXhrObject; - }); + callback = jasmine.createSpy('callback'); })); afterEach(function() { - // expect($exceptionHandler.errors.length).toBe(0); + if ($exceptionHandler.errors.length) throw $exceptionHandler.errors; + $httpBackend.verifyExpectations(); }); - function doCommonXhr(method, url) { - future = $http({method: method || 'GET', url: url || '/url'}); - - onSuccess = jasmine.createSpy('on200'); - onError = jasmine.createSpy('on400'); - future.on('200', onSuccess); - future.on('400', onError); - - return future; - } - it('should do basic request', function() { + $httpBackend.expect('GET', '/url').respond(''); $http({url: '/url', method: 'GET'}); - expect($browser.xhr).toHaveBeenCalledOnce(); - expect(url).toBe('/url'); - expect(method).toBe('GET'); }); it('should pass data if specified', function() { + $httpBackend.expect('POST', '/url', 'some-data').respond(''); $http({url: '/url', method: 'POST', data: 'some-data'}); - expect($browser.xhr).toHaveBeenCalledOnce(); - expect(data).toBe('some-data'); }); - it('should pass timeout if specified', function() { - $http({url: '/url', method: 'POST', timeout: 5000}); - expect($browser.xhr).toHaveBeenCalledOnce(); - expect(timeout).toBe(5000); - }); + // TODO(vojta): test passing timeout describe('callbacks', function() { - beforeEach(doCommonXhr); + function throwing(name) { + return function() { + throw name; + }; + } it('should log exceptions', function() { - onSuccess.andThrow('exception in success callback'); - onError.andThrow('exception in error callback'); + $httpBackend.expect('GET', '/url1').respond(200, 'content'); + $httpBackend.expect('GET', '/url2').respond(400, ''); - respond(200, 'content'); - expect($exceptionHandler.errors.pop()).toContain('exception in success callback'); + $http({url: '/url1', method: 'GET'}).on('200', throwing('exception in success callback')); + $http({url: '/url2', method: 'GET'}).on('400', throwing('exception in error callback')); + $httpBackend.flush(); - respond(400, ''); - expect($exceptionHandler.errors.pop()).toContain('exception in error callback'); + expect($exceptionHandler.errors.shift()).toContain('exception in success callback'); + expect($exceptionHandler.errors.shift()).toContain('exception in error callback'); }); it('should log more exceptions', function() { - onError.andThrow('exception in error callback'); - future.on('500', onError).on('50x', onError); - respond(500, ''); + $httpBackend.expect('GET', '/url').respond(500, ''); + $http({url: '/url', method: 'GET'}) + .on('500', throwing('exception in error callback')) + .on('5xx', throwing('exception in error callback')); + $httpBackend.flush(); expect($exceptionHandler.errors.length).toBe(2); $exceptionHandler.errors = []; @@ -102,82 +72,76 @@ describe('$http', function() { it('should get response as first param', function() { - respond(200, 'response'); - expect(onSuccess).toHaveBeenCalledOnce(); - expect(onSuccess.mostRecentCall.args[0]).toBe('response'); + $httpBackend.expect('GET', '/url').respond('some-content'); + $http({url: '/url', method: 'GET'}).on('200', callback); + $httpBackend.flush(); - respond(400, 'empty'); - expect(onError).toHaveBeenCalledOnce(); - expect(onError.mostRecentCall.args[0]).toBe('empty'); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('some-content'); }); it('should get status code as second param', function() { - respond(200, 'response'); - expect(onSuccess).toHaveBeenCalledOnce(); - expect(onSuccess.mostRecentCall.args[1]).toBe(200); + $httpBackend.expect('GET', '/url').respond(250, 'some-content'); + $http({url: '/url', method: 'GET'}).on('2xx', callback); + $httpBackend.flush(); - respond(400, 'empty'); - expect(onError).toHaveBeenCalledOnce(); - expect(onError.mostRecentCall.args[1]).toBe(400); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[1]).toBe(250); }); }); describe('response headers', function() { - var callback; - - beforeEach(function() { - callback = jasmine.createSpy('callback'); - }); - it('should return single header', function() { + $httpBackend.expect('GET', '/url').respond('', {'date': 'date-val'}); callback.andCallFake(function(r, s, header) { expect(header('date')).toBe('date-val'); }); $http({url: '/url', method: 'GET'}).on('200', callback); - respond(200, ''); + $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return null when single header does not exist', function() { + $httpBackend.expect('GET', '/url').respond('', {'Some-Header': 'Fake'}); callback.andCallFake(function(r, s, header) { header(); // we need that to get headers parsed first expect(header('nothing')).toBe(null); }); $http({url: '/url', method: 'GET'}).on('200', callback); - respond(200, ''); + $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return all headers as object', function() { + $httpBackend.expect('GET', '/url').respond('', {'content-encoding': 'gzip', 'server': 'Apache'}); callback.andCallFake(function(r, s, header) { expect(header()).toEqual({'content-encoding': 'gzip', 'server': 'Apache'}); }); $http({url: '/url', method: 'GET'}).on('200', callback); - respond(200, ''); + $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); it('should return empty object for jsonp request', function() { - // jsonp doesn't return raw object - rawXhrObject = undefined; callback.andCallFake(function(r, s, headers) { expect(headers()).toEqual({}); }); + $httpBackend.expect('JSONP', '/some').respond(200); $http({url: '/some', method: 'JSONP'}).on('200', callback); - respond(200, ''); + $httpBackend.flush(); expect(callback).toHaveBeenCalledOnce(); }); }); @@ -250,202 +214,192 @@ describe('$http', function() { describe('request headers', function() { it('should send custom headers', function() { + $httpBackend.expect('GET', '/url', undefined, function(headers) { + return headers['Custom'] == 'header' && headers['Content-Type'] == 'application/json'; + }).respond(''); + $http({url: '/url', method: 'GET', headers: { 'Custom': 'header', 'Content-Type': 'application/json' }}); - expect(headers['Custom']).toEqual('header'); - expect(headers['Content-Type']).toEqual('application/json'); + $httpBackend.flush(); }); it('should set default headers for GET request', function() { - $http({url: '/url', method: 'GET', headers: {}}); + $httpBackend.expect('GET', '/url', undefined, function(headers) { + return headers['Accept'] == 'application/json, text/plain, */*' && + headers['X-Requested-With'] == 'XMLHttpRequest'; + }).respond(''); - expect(headers['Accept']).toBe('application/json, text/plain, */*'); - expect(headers['X-Requested-With']).toBe('XMLHttpRequest'); + $http({url: '/url', method: 'GET', headers: {}}); + $httpBackend.flush(); }); it('should set default headers for POST request', function() { - $http({url: '/url', method: 'POST', headers: {}}); + $httpBackend.expect('POST', '/url', undefined, function(headers) { + return headers['Accept'] == 'application/json, text/plain, */*' && + headers['X-Requested-With'] == 'XMLHttpRequest' && + headers['Content-Type'] == 'application/json'; + }).respond(''); - expect(headers['Accept']).toBe('application/json, text/plain, */*'); - expect(headers['X-Requested-With']).toBe('XMLHttpRequest'); - expect(headers['Content-Type']).toBe('application/json'); + $http({url: '/url', method: 'POST', headers: {}}); + $httpBackend.flush(); }); it('should set default headers for PUT request', function() { - $http({url: '/url', method: 'PUT', headers: {}}); + $httpBackend.expect('PUT', '/url', undefined, function(headers) { + return headers['Accept'] == 'application/json, text/plain, */*' && + headers['X-Requested-With'] == 'XMLHttpRequest' && + headers['Content-Type'] == 'application/json'; + }).respond(''); - expect(headers['Accept']).toBe('application/json, text/plain, */*'); - expect(headers['X-Requested-With']).toBe('XMLHttpRequest'); - expect(headers['Content-Type']).toBe('application/json'); + $http({url: '/url', method: 'PUT', headers: {}}); + $httpBackend.flush(); }); it('should set default headers for custom HTTP method', function() { - $http({url: '/url', method: 'FOO', headers: {}}); + $httpBackend.expect('FOO', '/url', undefined, function(headers) { + return headers['Accept'] == 'application/json, text/plain, */*' && + headers['X-Requested-With'] == 'XMLHttpRequest'; + }).respond(''); - expect(headers['Accept']).toBe('application/json, text/plain, */*'); - expect(headers['X-Requested-With']).toBe('XMLHttpRequest'); + $http({url: '/url', method: 'FOO', headers: {}}); + $httpBackend.flush(); }); it('should override default headers with custom', function() { + $httpBackend.expect('POST', '/url', undefined, function(headers) { + return headers['Accept'] == 'Rewritten' && + headers['X-Requested-With'] == 'XMLHttpRequest' && + headers['Content-Type'] == 'Rewritten'; + }).respond(''); + $http({url: '/url', method: 'POST', headers: { 'Accept': 'Rewritten', 'Content-Type': 'Rewritten' }}); - - expect(headers['Accept']).toBe('Rewritten'); - expect(headers['X-Requested-With']).toBe('XMLHttpRequest'); - expect(headers['Content-Type']).toBe('Rewritten'); + $httpBackend.flush(); }); it('should set the XSRF cookie into a XSRF header', function() { + function checkXSRF(secret) { + return function(headers) { + return headers['X-XSRF-TOKEN'] == secret; + }; + } + $browser.cookies('XSRF-TOKEN', 'secret'); + $httpBackend.expect('GET', '/url', undefined, checkXSRF('secret')).respond(''); + $httpBackend.expect('POST', '/url', undefined, checkXSRF('secret')).respond(''); + $httpBackend.expect('PUT', '/url', undefined, checkXSRF('secret')).respond(''); + $httpBackend.expect('DELETE', '/url', undefined, checkXSRF('secret')).respond(''); $http({url: '/url', method: 'GET'}); - expect(headers['X-XSRF-TOKEN']).toBe('secret'); - $http({url: '/url', method: 'POST', headers: {'S-ome': 'Header'}}); - expect(headers['X-XSRF-TOKEN']).toBe('secret'); - $http({url: '/url', method: 'PUT', headers: {'Another': 'Header'}}); - expect(headers['X-XSRF-TOKEN']).toBe('secret'); - $http({url: '/url', method: 'DELETE', headers: {}}); - expect(headers['X-XSRF-TOKEN']).toBe('secret'); + + $httpBackend.flush(); }); }); describe('short methods', function() { - it('should have .get()', function() { - $http.get('/url'); + function checkHeader(name, value) { + return function(headers) { + return headers[name] == value; + }; + } - expect(method).toBe('GET'); - expect(url).toBe('/url'); + it('should have get()', function() { + $httpBackend.expect('GET', '/url').respond(''); + $http.get('/url'); }); - it('.get() should allow config param', function() { + it('get() should allow config param', function() { + $httpBackend.expect('GET', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.get('/url', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('GET'); - expect(url).toBe('/url'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .delete()', function() { + it('should have delete()', function() { + $httpBackend.expect('DELETE', '/url').respond(''); $http['delete']('/url'); - - expect(method).toBe('DELETE'); - expect(url).toBe('/url'); }); - it('.delete() should allow config param', function() { + it('delete() should allow config param', function() { + $httpBackend.expect('DELETE', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http['delete']('/url', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('DELETE'); - expect(url).toBe('/url'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .head()', function() { + it('should have head()', function() { + $httpBackend.expect('HEAD', '/url').respond(''); $http.head('/url'); - - expect(method).toBe('HEAD'); - expect(url).toBe('/url'); }); - it('.head() should allow config param', function() { + it('head() should allow config param', function() { + $httpBackend.expect('HEAD', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.head('/url', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('HEAD'); - expect(url).toBe('/url'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .patch()', function() { + it('should have patch()', function() { + $httpBackend.expect('PATCH', '/url').respond(''); $http.patch('/url'); - - expect(method).toBe('PATCH'); - expect(url).toBe('/url'); }); - it('.patch() should allow config param', function() { + it('patch() should allow config param', function() { + $httpBackend.expect('PATCH', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.patch('/url', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('PATCH'); - expect(url).toBe('/url'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .post()', function() { + it('should have post()', function() { + $httpBackend.expect('POST', '/url', 'some-data').respond(''); $http.post('/url', 'some-data'); - - expect(method).toBe('POST'); - expect(url).toBe('/url'); - expect(data).toBe('some-data'); }); - it('.post() should allow config param', function() { + it('post() should allow config param', function() { + $httpBackend.expect('POST', '/url', 'some-data', checkHeader('Custom', 'Header')).respond(''); $http.post('/url', 'some-data', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('POST'); - expect(url).toBe('/url'); - expect(data).toBe('some-data'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .put()', function() { + it('should have put()', function() { + $httpBackend.expect('PUT', '/url', 'some-data').respond(''); $http.put('/url', 'some-data'); - - expect(method).toBe('PUT'); - expect(url).toBe('/url'); - expect(data).toBe('some-data'); }); - it('.put() should allow config param', function() { + it('put() should allow config param', function() { + $httpBackend.expect('PUT', '/url', 'some-data', checkHeader('Custom', 'Header')).respond(''); $http.put('/url', 'some-data', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('PUT'); - expect(url).toBe('/url'); - expect(data).toBe('some-data'); - expect(headers['Custom']).toBe('Header'); }); - it('should have .jsonp()', function() { + it('should have jsonp()', function() { + $httpBackend.expect('JSONP', '/url').respond(''); $http.jsonp('/url'); - - expect(method).toBe('JSONP'); - expect(url).toBe('/url'); }); - it('.jsonp() should allow config param', function() { + it('jsonp() should allow config param', function() { + $httpBackend.expect('JSONP', '/url', undefined, checkHeader('Custom', 'Header')).respond(''); $http.jsonp('/url', {headers: {'Custom': 'Header'}}); - - expect(method).toBe('JSONP'); - expect(url).toBe('/url'); - expect(headers['Custom']).toBe('Header'); }); }); @@ -454,7 +408,14 @@ describe('$http', function() { describe('abort', function() { - beforeEach(doCommonXhr); + var future, rawXhrObject; + + beforeEach(function() { + $httpBackend.when('GET', '/url').then(''); + future = $http({method: 'GET', url: '/url'}); + rawXhrObject = MockXhr.$$lastInstance; + spyOn(rawXhrObject, 'abort'); + }); it('should return itself to allow chaining', function() { expect(future.abort()).toBe(future); @@ -468,7 +429,7 @@ describe('$http', function() { it('should not abort already finished request', function() { - respond(200, 'content'); + $httpBackend.flush(); future.abort(); expect(rawXhrObject.abort).not.toHaveBeenCalled(); @@ -478,31 +439,33 @@ describe('$http', function() { describe('retry', function() { + var future; + + beforeEach(function() { + $httpBackend.expect('HEAD', '/url-x').respond(''); + future = $http({method: 'HEAD', url: '/url-x'}).on('2xx', callback); + }); + it('should retry last request with same callbacks', function() { - doCommonXhr('HEAD', '/url-x'); - respond(200, ''); - $browser.xhr.reset(); - onSuccess.reset(); + $httpBackend.flush(); + callback.reset(); + $httpBackend.expect('HEAD', '/url-x').respond(''); future.retry(); - expect($browser.xhr).toHaveBeenCalledOnce(); - expect(method).toBe('HEAD'); - expect(url).toBe('/url-x'); - - respond(200, 'body'); - expect(onSuccess).toHaveBeenCalledOnce(); + $httpBackend.flush(); + expect(callback).toHaveBeenCalledOnce(); }); it('should return itself to allow chaining', function() { - doCommonXhr(); - respond(200, ''); + $httpBackend.flush(); + + $httpBackend.expect('HEAD', '/url-x').respond(''); expect(future.retry()).toBe(future); }); it('should throw error when pending request', function() { - doCommonXhr(); expect(future.retry).toThrow('Can not retry request. Abort pending request first.'); }); }); @@ -510,98 +473,92 @@ describe('$http', function() { describe('on', function() { - var callback; + var future; + + function expectToMatch(status, pattern) { + expectToNotMatch(status, pattern, true); + } + + function expectToNotMatch(status, pattern, match) { + callback.reset(); + future = $http({method: 'GET', url: '/' + status}); + future.on(pattern, callback); + $httpBackend.flush(); + + if (match) expect(callback).toHaveBeenCalledOnce(); + else expect(callback).not.toHaveBeenCalledOnce(); + } beforeEach(function() { - future = $http({method: 'GET', url: '/url'}); - callback = jasmine.createSpy('callback'); + $httpBackend.when('GET').then(function(m, url) { + return [parseInt(url.substr(1)), '', {}]; + }); }); it('should return itself to allow chaining', function() { + future = $http({method: 'GET', url: '/url'}); expect(future.on('200', noop)).toBe(future); }); it('should call exact status code callback', function() { - future.on('205', callback); - respond(205, ''); - - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(205, '205'); }); it('should match 2xx', function() { - future.on('2xx', callback); - - respond(200, ''); - respond(201, ''); - respond(266, ''); + expectToMatch(200, '2xx'); + expectToMatch(201, '2xx'); + expectToMatch(266, '2xx'); - respond(400, ''); - respond(300, ''); - - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(3); + expectToNotMatch(400, '2xx'); + expectToNotMatch(300, '2xx'); }); it('should match 20x', function() { - future.on('20x', callback); - - respond(200, ''); - respond(201, ''); - respond(205, ''); - - respond(400, ''); - respond(300, ''); - respond(210, ''); - respond(255, ''); + expectToMatch(200, '20x'); + expectToMatch(201, '20x'); + expectToMatch(205, '20x'); - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(3); + expectToNotMatch(210, '20x'); + expectToNotMatch(301, '20x'); + expectToNotMatch(404, '20x'); + expectToNotMatch(501, '20x'); }); it('should match 2x1', function() { - future.on('2x1', callback); - - respond(201, ''); - respond(211, ''); - respond(251, ''); - - respond(400, ''); - respond(300, ''); - respond(210, ''); - respond(255, ''); + expectToMatch(201, '2x1'); + expectToMatch(211, '2x1'); + expectToMatch(251, '2x1'); - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(3); + expectToNotMatch(210, '2x1'); + expectToNotMatch(301, '2x1'); + expectToNotMatch(400, '2x1'); }); it('should match xxx', function() { - future.on('xxx', callback); - - respond(201, ''); - respond(211, ''); - respond(251, ''); - respond(404, ''); - respond(501, ''); - - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(5); + expectToMatch(200, 'xxx'); + expectToMatch(210, 'xxx'); + expectToMatch(301, 'xxx'); + expectToMatch(406, 'xxx'); + expectToMatch(510, 'xxx'); }); it('should call all matched callbacks', function() { var no = jasmine.createSpy('wrong'); - future.on('xxx', callback); - future.on('2xx', callback); - future.on('205', callback); - future.on('3xx', no); - future.on('2x1', no); - future.on('4xx', no); - respond(205, ''); + $http({method: 'GET', url: '/205'}) + .on('xxx', callback) + .on('2xx', callback) + .on('205', callback) + .on('3xx', no) + .on('2x1', no) + .on('4xx', no); + + $httpBackend.flush(); expect(callback).toHaveBeenCalled(); expect(callback.callCount).toBe(3); @@ -610,98 +567,66 @@ describe('$http', function() { it('should allow list of status patterns', function() { - future.on('2xx,3xx', callback); - - respond(405, ''); - expect(callback).not.toHaveBeenCalled(); - - respond(201); - expect(callback).toHaveBeenCalledOnce(); - - respond(301); - expect(callback.callCount).toBe(2); + expectToMatch(201, '2xx,3xx'); + expectToMatch(301, '2xx,3xx'); + expectToNotMatch(405, '2xx,3xx'); }); it('should preserve the order of listeners', function() { var log = ''; - future.on('2xx', function() {log += '1';}); - future.on('201', function() {log += '2';}); - future.on('2xx', function() {log += '3';}); - respond(201); + $http({method: 'GET', url: '/201'}) + .on('2xx', function() {log += '1';}) + .on('201', function() {log += '2';}) + .on('2xx', function() {log += '3';}); + + $httpBackend.flush(); expect(log).toBe('123'); }); it('should know "success" alias', function() { - future.on('success', callback); - respond(200, ''); - expect(callback).toHaveBeenCalledOnce(); - - callback.reset(); - respond(201, ''); - expect(callback).toHaveBeenCalledOnce(); - - callback.reset(); - respond(250, ''); - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(200, 'success'); + expectToMatch(201, 'success'); + expectToMatch(250, 'success'); - callback.reset(); - respond(404, ''); - respond(501, ''); - expect(callback).not.toHaveBeenCalled(); + expectToNotMatch(403, 'success'); + expectToNotMatch(501, 'success'); }); it('should know "error" alias', function() { - future.on('error', callback); - respond(401, ''); - expect(callback).toHaveBeenCalledOnce(); - - callback.reset(); - respond(500, ''); - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(401, 'error'); + expectToMatch(500, 'error'); + expectToMatch(0, 'error'); - callback.reset(); - respond(0, ''); - expect(callback).toHaveBeenCalledOnce(); - - callback.reset(); - respond(201, ''); - respond(200, ''); - respond(300, ''); - expect(callback).not.toHaveBeenCalled(); + expectToNotMatch(201, 'error'); + expectToNotMatch(200, 'error'); }); it('should know "always" alias', function() { - future.on('always', callback); - respond(201, ''); - respond(200, ''); - respond(300, ''); - respond(401, ''); - respond(502, ''); - respond(0, ''); - respond(-1, ''); - respond(-2, ''); - - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(8); + expectToMatch(200, 'always'); + expectToMatch(201, 'always'); + expectToMatch(250, 'always'); + expectToMatch(300, 'always'); + expectToMatch(302, 'always'); + expectToMatch(404, 'always'); + expectToMatch(501, 'always'); + expectToMatch(0, 'always'); + expectToMatch(-1, 'always'); + expectToMatch(-2, 'always'); }); it('should call "xxx" when 0 status code', function() { - future.on('xxx', callback); - respond(0, ''); - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(0, 'xxx'); }); it('should not call "2xx" when 0 status code', function() { - future.on('2xx', callback); - respond(0, ''); - expect(callback).not.toHaveBeenCalled(); + expectToNotMatch(0, '2xx'); }); it('should normalize internal statuses -1, -2 to 0', function() { @@ -709,36 +634,27 @@ describe('$http', function() { expect(status).toBe(0); }); - future.on('xxx', callback); - respond(-1, ''); - respond(-2, ''); + $http({method: 'GET', url: '/0'}).on('xxx', callback); + $http({method: 'GET', url: '/-1'}).on('xxx', callback); + $http({method: 'GET', url: '/-2'}).on('xxx', callback); + $httpBackend.flush(); expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(2); + expect(callback.callCount).toBe(3); }); it('should match "timeout" when -1 internal status', function() { - future.on('timeout', callback); - respond(-1, ''); - - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(-1, 'timeout'); }); it('should match "abort" when 0 status', function() { - future.on('abort', callback); - respond(0, ''); - - expect(callback).toHaveBeenCalledOnce(); + expectToMatch(0, 'abort'); }); it('should match "error" when 0, -1, or -2', function() { - future.on('error', callback); - respond(0, ''); - respond(-1, ''); - respond(-2, ''); - - expect(callback).toHaveBeenCalled(); - expect(callback.callCount).toBe(3); + expectToMatch(0, 'error'); + expectToMatch(-1, 'error'); + expectToMatch(-2, 'error'); }); }); }); @@ -746,29 +662,28 @@ describe('$http', function() { describe('scope.$apply', function() { - beforeEach(doCommonXhr); - it('should $apply after success callback', function() { - respond(200, ''); + $httpBackend.when('GET').then(200); + $http({method: 'GET', url: '/some'}); + $httpBackend.flush(); expect(scope.$apply).toHaveBeenCalledOnce(); }); it('should $apply after error callback', function() { - respond(404, ''); + $httpBackend.when('GET').then(404); + $http({method: 'GET', url: '/some'}); + $httpBackend.flush(); expect(scope.$apply).toHaveBeenCalledOnce(); }); it('should $apply even if exception thrown during callback', function() { - onSuccess.andThrow('error in callback'); - onError.andThrow('error in callback'); - - respond(200, ''); - expect(scope.$apply).toHaveBeenCalledOnce(); + $httpBackend.when('GET').then(200); + callback.andThrow('error in callback'); - scope.$apply.reset(); - respond(400, ''); + $http({method: 'GET', url: '/some'}).on('200', callback); + $httpBackend.flush(); expect(scope.$apply).toHaveBeenCalledOnce(); $exceptionHandler.errors = []; @@ -783,14 +698,14 @@ describe('$http', function() { describe('default', function() { it('should transform object into json', function() { + $httpBackend.expect('POST', '/url', '{"one":"two"}').respond(''); $http({method: 'POST', url: '/url', data: {one: 'two'}}); - expect(data).toBe('{"one":"two"}'); }); it('should ignore strings', function() { + $httpBackend.expect('POST', '/url', 'string-data').respond(''); $http({method: 'POST', url: '/url', data: 'string-data'}); - expect(data).toBe('string-data'); }); }); }); @@ -801,40 +716,47 @@ describe('$http', function() { describe('default', function() { it('should deserialize json objects', function() { - doCommonXhr(); - respond(200, '{"foo":"bar","baz":23}'); + $httpBackend.expect('GET', '/url').respond('{"foo":"bar","baz":23}'); + $http({method: 'GET', url: '/url'}).on('200', callback); + $httpBackend.flush(); - expect(onSuccess.mostRecentCall.args[0]).toEqual({foo: 'bar', baz: 23}); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toEqual({foo: 'bar', baz: 23}); }); it('should deserialize json arrays', function() { - doCommonXhr(); - respond(200, '[1, "abc", {"foo":"bar"}]'); + $httpBackend.expect('GET', '/url').respond('[1, "abc", {"foo":"bar"}]'); + $http({method: 'GET', url: '/url'}).on('200', callback); + $httpBackend.flush(); - expect(onSuccess.mostRecentCall.args[0]).toEqual([1, 'abc', {foo: 'bar'}]); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toEqual([1, 'abc', {foo: 'bar'}]); }); it('should deserialize json with security prefix', function() { - doCommonXhr(); - respond(200, ')]}\',\n[1, "abc", {"foo":"bar"}]'); + $httpBackend.expect('GET', '/url').respond(')]}\',\n[1, "abc", {"foo":"bar"}]'); + $http({method: 'GET', url: '/url'}).on('200', callback); + $httpBackend.flush(); - expect(onSuccess.mostRecentCall.args[0]).toEqual([1, 'abc', {foo:'bar'}]); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toEqual([1, 'abc', {foo:'bar'}]); }); }); + it('should pipeline more functions', function() { function first(d) {return d + '1';} function second(d) {return d + '2';} - onSuccess = jasmine.createSpy('onSuccess'); - $http({method: 'POST', url: '/url', data: '0', transformResponse: [first, second]}) - .on('200', onSuccess); + $httpBackend.expect('POST', '/url').respond('0'); + $http({method: 'POST', url: '/url', transformResponse: [first, second]}) + .on('200', callback); + $httpBackend.flush(); - respond(200, '0'); - expect(onSuccess).toHaveBeenCalledOnce(); - expect(onSuccess.mostRecentCall.args[0]).toBe('012'); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('012'); }); }); }); @@ -842,95 +764,100 @@ describe('$http', function() { describe('cache', function() { - function doFirstCacheRequest(method, responseStatus) { - onSuccess = jasmine.createSpy('on200'); - $http({method: method || 'get', url: '/url', cache: true}); - respond(responseStatus || 200, 'content'); - $browser.xhr.reset(); + function doFirstCacheRequest(method, respStatus, headers) { + $httpBackend.expect(method || 'GET', '/url').respond(respStatus || 200, 'content', headers); + $http({method: method || 'GET', url: '/url', cache: true}); + $httpBackend.flush(); } it('should cache GET request', function() { doFirstCacheRequest(); - $http({method: 'get', url: '/url', cache: true}).on('200', onSuccess); + $http({method: 'get', url: '/url', cache: true}).on('200', callback); $browser.defer.flush(); - expect(onSuccess).toHaveBeenCalledOnce(); - expect(onSuccess.mostRecentCall.args[0]).toBe('content'); - expect($browser.xhr).not.toHaveBeenCalled(); + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('content'); }); it('should always call callback asynchronously', function() { doFirstCacheRequest(); + $http({method: 'get', url: '/url', cache: true}).on('200', callback); - $http({method: 'get', url: '/url', cache: true}).on('200', onSuccess); - expect(onSuccess).not.toHaveBeenCalled(); + expect(callback).not.toHaveBeenCalledOnce(); }); it('should not cache POST request', function() { - doFirstCacheRequest('post'); + doFirstCacheRequest('POST'); - $http({method: 'post', url: '/url', cache: true}).on('200', onSuccess); - $browser.defer.flush(); - expect(onSuccess).not.toHaveBeenCalled(); - expect($browser.xhr).toHaveBeenCalledOnce(); + $httpBackend.expect('POST', '/url').respond('content2'); + $http({method: 'POST', url: '/url', cache: true}).on('200', callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should not cache PUT request', function() { - doFirstCacheRequest('put'); + doFirstCacheRequest('PUT'); - $http({method: 'put', url: '/url', cache: true}).on('200', onSuccess); - $browser.defer.flush(); - expect(onSuccess).not.toHaveBeenCalled(); - expect($browser.xhr).toHaveBeenCalledOnce(); + $httpBackend.expect('PUT', '/url').respond('content2'); + $http({method: 'PUT', url: '/url', cache: true}).on('200', callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should not cache DELETE request', function() { - doFirstCacheRequest('delete'); + doFirstCacheRequest('DELETE'); - $http({method: 'delete', url: '/url', cache: true}).on('200', onSuccess); - $browser.defer.flush(); - expect(onSuccess).not.toHaveBeenCalled(); - expect($browser.xhr).toHaveBeenCalledOnce(); + $httpBackend.expect('DELETE', '/url').respond(206); + $http({method: 'DELETE', url: '/url', cache: true}).on('206', callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnce(); }); it('should not cache non 2xx responses', function() { - doFirstCacheRequest('get', 404); + doFirstCacheRequest('GET', 404); - $http({method: 'get', url: '/url', cache: true}).on('200', onSuccess); - $browser.defer.flush(); - expect(onSuccess).not.toHaveBeenCalled(); - expect($browser.xhr).toHaveBeenCalledOnce(); + $httpBackend.expect('GET', '/url').respond('content2'); + $http({method: 'GET', url: '/url', cache: true}).on('200', callback); + $httpBackend.flush(); + + expect(callback).toHaveBeenCalledOnce(); + expect(callback.mostRecentCall.args[0]).toBe('content2'); }); it('should cache the headers as well', function() { - doFirstCacheRequest(); - onSuccess.andCallFake(function(r, s, headers) { + doFirstCacheRequest('GET', 200, {'content-encoding': 'gzip', 'server': 'Apache'}); + callback.andCallFake(function(r, s, headers) { expect(headers()).toEqual({'content-encoding': 'gzip', 'server': 'Apache'}); expect(headers('server')).toBe('Apache'); }); - $http({method: 'get', url: '/url', cache: true}).on('200', onSuccess); + $http({method: 'GET', url: '/url', cache: true}).on('200', callback); $browser.defer.flush(); - expect(onSuccess).toHaveBeenCalledOnce(); + expect(callback).toHaveBeenCalledOnce(); }); it('should cache status code as well', function() { - doFirstCacheRequest('get', 201); - onSuccess.andCallFake(function(r, status, h) { + doFirstCacheRequest('GET', 201); + callback.andCallFake(function(r, status, h) { expect(status).toBe(201); }); - $http({method: 'get', url: '/url', cache: true}).on('2xx', onSuccess); + $http({method: 'get', url: '/url', cache: true}).on('2xx', callback); $browser.defer.flush(); - expect(onSuccess).toHaveBeenCalledOnce(); + expect(callback).toHaveBeenCalledOnce(); }); }); @@ -938,29 +865,34 @@ describe('$http', function() { describe('pendingCount', function() { it('should return number of pending requests', function() { + $httpBackend.when('GET').then(200); expect($http.pendingCount()).toBe(0); $http({method: 'get', url: '/some'}); expect($http.pendingCount()).toBe(1); - respond(200, ''); + $httpBackend.flush(); expect($http.pendingCount()).toBe(0); }); it('should decrement the counter when request aborted', function() { + $httpBackend.when('GET').then(0); future = $http({method: 'get', url: '/x'}); expect($http.pendingCount()).toBe(1); + future.abort(); - respond(0, ''); + $httpBackend.flush(); expect($http.pendingCount()).toBe(0); }); it('should decrement the counter when served from cache', function() { + $httpBackend.when('GET').then(200); + $http({method: 'get', url: '/cached', cache: true}); - respond(200, 'content'); + $httpBackend.flush(); expect($http.pendingCount()).toBe(0); $http({method: 'get', url: '/cached', cache: true}); @@ -972,12 +904,13 @@ describe('$http', function() { it('should decrement the counter before firing callbacks', function() { - $http({method: 'get', url: '/cached'}).on('xxx', function() { + $httpBackend.when('GET').then(200); + $http({method: 'get', url: '/url'}).on('xxx', function() { expect($http.pendingCount()).toBe(0); }); expect($http.pendingCount()).toBe(1); - respond(200, 'content'); + $httpBackend.flush(); }); }); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 2ddb26e1..c3bc1333 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -134,14 +134,13 @@ describe("widget", function() { expect($rootScope.$$childHead).toBeFalsy(); })); - it('should do xhr request and cache it', inject(function($rootScope, $browser, $compile) { + it('should do xhr request and cache it', inject(function($rootScope, $httpBackend, $compile) { var element = $compile('')($rootScope); - var $browserXhr = $browser.xhr; - $browserXhr.expectGET('myUrl').respond('my partial'); + $httpBackend.expect('GET', 'myUrl').respond('my partial'); $rootScope.url = 'myUrl'; $rootScope.$digest(); - $browserXhr.flush(); + $httpBackend.flush(); expect(element.text()).toEqual('my partial'); $rootScope.url = null; @@ -155,14 +154,13 @@ describe("widget", function() { })); it('should clear content when error during xhr request', - inject(function($browser, $compile, $rootScope) { + inject(function($httpBackend, $compile, $rootScope) { var element = $compile('content')($rootScope); - var $browserXhr = $browser.xhr; - $browserXhr.expectGET('myUrl').respond(404, ''); + $httpBackend.expect('GET', 'myUrl').respond(404, ''); $rootScope.url = 'myUrl'; $rootScope.$digest(); - $browserXhr.flush(); + $httpBackend.flush(); expect(element.text()).toBe(''); })); @@ -500,33 +498,33 @@ describe("widget", function() { it('should load content via xhr when route changes', - inject(function($rootScope, $compile, $browser, $location, $route) { + inject(function($rootScope, $compile, $httpBackend, $location, $route) { $route.when('/foo', {template: 'myUrl1'}); $route.when('/bar', {template: 'myUrl2'}); expect(element.text()).toEqual(''); $location.path('/foo'); - $browser.xhr.expectGET('myUrl1').respond('
{{1+3}}
'); + $httpBackend.expect('GET', 'myUrl1').respond('
{{1+3}}
'); $rootScope.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect(element.text()).toEqual('4'); $location.path('/bar'); - $browser.xhr.expectGET('myUrl2').respond('angular is da best'); + $httpBackend.expect('GET', 'myUrl2').respond('angular is da best'); $rootScope.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect(element.text()).toEqual('angular is da best'); })); it('should remove all content when location changes to an unknown route', - inject(function($rootScope, $compile, $location, $browser, $route) { + inject(function($rootScope, $compile, $location, $httpBackend, $route) { $route.when('/foo', {template: 'myUrl1'}); $location.path('/foo'); - $browser.xhr.expectGET('myUrl1').respond('
{{1+3}}
'); + $httpBackend.expect('GET', 'myUrl1').respond('
{{1+3}}
'); $rootScope.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect(element.text()).toEqual('4'); $location.path('/unknown'); @@ -535,14 +533,14 @@ describe("widget", function() { })); it('should chain scopes and propagate evals to the child scope', - inject(function($rootScope, $compile, $location, $browser, $route) { + inject(function($rootScope, $compile, $location, $httpBackend, $route) { $route.when('/foo', {template: 'myUrl1'}); $rootScope.parentVar = 'parent'; $location.path('/foo'); - $browser.xhr.expectGET('myUrl1').respond('
{{parentVar}}
'); + $httpBackend.expect('GET', 'myUrl1').respond('
{{parentVar}}
'); $rootScope.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect(element.text()).toEqual('parent'); $rootScope.parentVar = 'new parent'; @@ -551,10 +549,11 @@ describe("widget", function() { })); it('should be possible to nest ng:view in ng:include', inject(function() { + // TODO(vojta): refactor this test var injector = angular.injector('ng', 'ngMock'); var myApp = injector.get('$rootScope'); - var $browser = injector.get('$browser'); - $browser.xhr.expectGET('includePartial.html').respond('view: '); + var $httpBackend = injector.get('$httpBackend'); + $httpBackend.expect('GET', 'includePartial.html').respond('view: '); injector.get('$location').path('/foo'); var $route = injector.get('$route'); @@ -566,9 +565,10 @@ describe("widget", function() { '')(myApp); myApp.$apply(); - $browser.xhr.expectGET('viewPartial.html').respond('content'); + $httpBackend.expect('GET', 'viewPartial.html').respond('content'); + $httpBackend.flush(); myApp.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect(myApp.$element.text()).toEqual('include: view: content'); expect($route.current.template).toEqual('viewPartial.html'); @@ -576,11 +576,10 @@ describe("widget", function() { })); it('should initialize view template after the view controller was initialized even when ' + - 'templates were cached', inject(function($rootScope, $compile, $location, $browser, $route) { + 'templates were cached', inject(function($rootScope, $compile, $location, $httpBackend, $route) { //this is a test for a regression that was introduced by making the ng:view cache sync $route.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'}); - $rootScope.log = []; function ParentCtrl() { @@ -592,12 +591,12 @@ describe("widget", function() { }; $location.path('/foo'); - $browser.xhr.expectGET('viewPartial.html'). + $httpBackend.expect('GET', 'viewPartial.html'). respond('
' + '
' + '
'); $rootScope.$apply(); - $browser.xhr.flush(); + $httpBackend.flush(); expect($rootScope.log).toEqual(['parent', 'init', 'child']); @@ -608,13 +607,12 @@ describe("widget", function() { $rootScope.log = []; $location.path('/foo'); $rootScope.$apply(); - $browser.defer.flush(); expect($rootScope.log).toEqual(['parent', 'init', 'child']); })); it('should discard pending xhr callbacks if a new route is requested before the current ' + - 'finished loading', inject(function($route, $rootScope, $location, $browser) { + 'finished loading', inject(function($route, $rootScope, $location, $httpBackend) { // this is a test for a bad race condition that affected feedback $route.when('/foo', {template: 'myUrl1'}); @@ -623,26 +621,26 @@ describe("widget", function() { expect($rootScope.$element.text()).toEqual(''); $location.path('/foo'); - $browser.xhr.expectGET('myUrl1').respond('
{{1+3}}
'); + $httpBackend.expect('GET', 'myUrl1').respond('
{{1+3}}
'); $rootScope.$digest(); $location.path('/bar'); - $browser.xhr.expectGET('myUrl2').respond('
{{1+1}}
'); + $httpBackend.expect('GET', 'myUrl2').respond('
{{1+1}}
'); $rootScope.$digest(); - $browser.xhr.flush(); // now that we have to requests pending, flush! + $httpBackend.flush(); // now that we have to requests pending, flush! expect($rootScope.$element.text()).toEqual('2'); })); it('should clear the content when error during xhr request', - inject(function($route, $location, $rootScope, $browser) { + inject(function($route, $location, $rootScope, $httpBackend) { $route.when('/foo', {controller: noop, template: 'myUrl1'}); $location.path('/foo'); - $browser.xhr.expectGET('myUrl1').respond(404, ''); + $httpBackend.expect('GET', 'myUrl1').respond(404, ''); $rootScope.$element.text('content'); $rootScope.$digest(); - $browser.xhr.flush(); + $httpBackend.flush(); expect($rootScope.$element.text()).toBe(''); })); -- cgit v1.2.3