feat($http): turn mock backend into a decorator + e2e testing support

- provider -> decorator
- autoflush + passThrough mode
- fix noop -> angular.noop
This commit is contained in:
Igor Minar 2012-01-06 18:39:03 -08:00 committed by Vojta Jina
parent 23f8da7cbb
commit 67338ce061
2 changed files with 173 additions and 122 deletions

250
src/angular-mocks.js vendored
View file

@ -21,7 +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);
$provide.decorator('$httpBackend', angular.module.ngMock.$httpBackendDecorator);
};
angular.module.ngMock.$inject = ['$provide'];
@ -56,8 +56,8 @@ angular.module.ngMock.$Browser = function() {
self.pollFns = [];
// TODO(vojta): remove this temporary api
self.$$completeOutstandingRequest = noop;
self.$$incOutstandingRequestCount = noop;
self.$$completeOutstandingRequest = angular.noop;
self.$$incOutstandingRequestCount = angular.noop;
// register url polling fn
@ -593,144 +593,160 @@ angular.module.ngMock.dump = function(object){
* respond with static or dynamic responses via the `expect` and `when` apis and their shortcuts
* (`expectGET`, `whenPOST`, etc).
*/
angular.module.ngMock.$HttpBackendProvider = function() {
this.$get = function() {
var definitions = [],
expectations = [],
responses = [];
angular.module.ngMock.$httpBackendDecorator = function($delegate, $defer) {
var definitions = [],
expectations = [],
responses = [],
responsesPush = angular.bind(responses, responses.push),
autoflush = false;
function createResponse(status, data, headers) {
if (isFunction(status)) return status;
function createResponse(status, data, headers) {
if (angular.isFunction(status)) return status;
return function() {
return angular.isNumber(status)
? [status, data, headers]
: [200, status, data];
}
return function() {
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;
function prettyPrint(data) {
return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
? data
: angular.toJson(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 ' + expectation + ' with different data\n' +
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
function prettyPrint(data) {
return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
? data
: angular.toJson(data);
if (!expectation.matchHeaders(headers))
throw Error('Expected ' + expectation + ' with different headers\n' +
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers));
expectations.shift();
if (expectation.response) {
responses.push(function() {
var response = expectation.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(response[0], response[1], xhr.getAllResponseHeaders());
});
return;
}
wasExpected = true;
}
if (expectation && expectation.match(method, url)) {
if (!expectation.matchData(data))
throw Error('Expected ' + expectation + ' with different data\n' +
'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
if (!expectation.matchHeaders(headers))
throw Error('Expected ' + expectation + ' with different headers\n' +
'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers));
expectations.shift();
if (expectation.response) {
responses.push(function() {
var response = expectation.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(response[0], response[1], xhr.getAllResponseHeaders());
});
return;
}
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 i = -1, definition;
while ((definition = definitions[++i])) {
if (definition.match(method, url, data, headers || {})) {
if (definition.response) {
(autoflush ? $defer : responsesPush)(function() {
var response = definition.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(response[0], response[1], xhr.getAllResponseHeaders());
});
return;
}
} else if (definition.passThrough) {
$delegate(method, url, data, callback, headers);
} else throw Error('No response defined !');
return;
}
throw wasExpected ?
Error('No response defined !') :
Error('Unexpected request: ' + method + ' ' + url + '\n' +
(expectation ? 'Expected ' + expectation : 'No more request expected'));
}
throw wasExpected ?
Error('No response defined !') :
Error('Unexpected request: ' + method + ' ' + url + '\n' +
(expectation ? 'Expected ' + expectation : 'No more request expected'));
}
$httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers);
definitions.push(definition);
return {
respond: function(status, data, headers) {
definition.response = createResponse(status, data, headers);
}
};
};
$httpBackend.when = function(method, url, data, headers) {
var definition = new MockHttpExpectation(method, url, data, headers);
definitions.push(definition);
return {
respond: function(status, data, headers) {
definition.response = createResponse(status, data, headers);
},
createShortMethods('when');
$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);
}
};
};
createShortMethods('expect');
$httpBackend.flush = function(count) {
if (!responses.length) throw Error('No pending request to flush !');
if (angular.isDefined(count)) {
while (count--) {
if (!responses.length) throw Error('No more pending request to flush !');
responses.shift()();
}
} else {
while (responses.length) {
responses.shift()();
}
}
$httpBackend.verifyNoOutstandingExpectation();
};
$httpBackend.verifyNoOutstandingExpectation = function() {
if (expectations.length) {
throw Error('Unsatisfied requests: ' + expectations.join(', '));
passThrough: function() {
definition.passThrough = true;
}
};
};
$httpBackend.verifyNoOutstandingRequest = function() {
if (responses.length) {
throw Error('Unflushed requests: ' + responses.length);
createShortMethods('when');
$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.resetExpectations = function() {
expectations = [];
responses = [];
};
return $httpBackend;
createShortMethods('expect');
function createShortMethods(prefix) {
angular.forEach(['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'JSONP'], function(method) {
$httpBackend[prefix + method] = function(url, data, headers) {
return $httpBackend[prefix](method, url, data, headers)
}
});
$httpBackend.flush = function(count) {
if (!responses.length) throw Error('No pending request to flush !');
if (angular.isDefined(count)) {
while (count--) {
if (!responses.length) throw Error('No more pending request to flush !');
responses.shift()();
}
} else {
while (responses.length) {
responses.shift()();
}
}
$httpBackend.verifyNoOutstandingExpectation();
};
$httpBackend.autoflush = function(val) {
if (arguments.length) {
autoflush = !!val;
} else {
return autoflush;
}
}
$httpBackend.verifyNoOutstandingExpectation = function() {
if (expectations.length) {
throw Error('Unsatisfied requests: ' + expectations.join(', '));
}
};
$httpBackend.verifyNoOutstandingRequest = function() {
if (responses.length) {
throw Error('Unflushed requests: ' + responses.length);
}
};
$httpBackend.resetExpectations = function() {
expectations = [];
responses = [];
};
return $httpBackend;
function createShortMethods(prefix) {
angular.forEach(['GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'JSONP'], function(method) {
$httpBackend[prefix + method] = function(url, data, headers) {
return $httpBackend[prefix](method, url, data, headers)
}
});
}
};
function MockHttpExpectation(method, url, data, headers) {
@ -816,7 +832,7 @@ function MockXhr() {
return lines.join('\n');
};
this.abort = noop;
this.abort = angular.noop;
}
window.jstestdriver && (function(window){

View file

@ -375,12 +375,19 @@ describe('mocks', function() {
describe('$httpBackend', function() {
var hb, callback;
var hb, callback, realBackendSpy;
beforeEach(inject(function($httpBackend) {
callback = jasmine.createSpy('callback');
hb = $httpBackend;
}));
beforeEach(inject(
function($provide) {
realBackendSpy = jasmine.createSpy('realBackend');
$provide.value('$httpBackend', realBackendSpy);
$provide.decorator('$httpBackend', angular.module.ngMock.$httpBackendDecorator)
},
function($httpBackend) {
callback = jasmine.createSpy('callback');
hb = $httpBackend;
}
));
it('should respond with first matched definition', function() {
@ -676,6 +683,34 @@ describe('mocks', function() {
});
describe('definitions with passThrough delegation', function() {
it('should delegate requests to the real backend when passThrough is invoked', function() {
hb.when('GET', /\/passThrough\/.*/).passThrough();
expect(hb('GET', '/passThrough/23', null, callback));
expect(realBackendSpy).
toHaveBeenCalledOnceWith('GET', '/passThrough/23', null, callback, undefined);
});
});
describe('autoflush', function() {
it('should flush responses via $defer when autoflush is turned on', inject(
function($browser) {
expect(hb.autoflush()).toBe(false);
hb.autoflush(true);
expect(hb.autoflush()).toBe(true);
hb.when('GET', '/foo').respond('bar');
hb('GET', '/foo', null, callback);
expect(callback).not.toHaveBeenCalled();
$browser.defer.flush();
expect(callback).toHaveBeenCalledOnce();
}));
});
describe('verifyExpectations', function() {
it('should throw exception if not all expectations were satisfied', function() {