I am currently writing tests for some services using karma
with jasmine
, and I was wondering if I had to mock a service's service dependency that uses $http
, as described below.
PS : I'm already using $httpBackend
to mock any GET request, and I plan on using $httpBackend.expect*
if I don't mock the service ApiProvider
.factory('CRUDService', ['ApiProvider', function (ApiProvider) {
'use strict';
var CRUD = function CRUD(modelName) {
this.getModelName = function () {
return modelName;
};
},
overridableMethods = {
save: null
};
CRUD.prototype = {
save: function () {
// ABSTRACT
},
/**
* Deletes instance from id property
* @return http promise
*/
remove: function () {
return ApiProvider.delete(this.getModelName(), this.id);
}
};
return {
/**
* Function creating a class extending CRUD
* @param {string} modelName
* @param {Object} methods an object with methods to override, ex: save
* return {classD} the extended class
*/
build: function (modelName, methods) {
var key,
Model = function () {
};
// Class extending CRUD
Model.prototype = new CRUD(modelName);
// Apply override on methods allowed for override
for (key in methods) {
if (key in overridableMethods &&
typeof methods[key] === 'function') {
Model.prototype[key] = methods[key];
}
}
/**
* Static method
* Gets an entity of a model
* @param {Object} config @see ApiProvider config
* @return {CRUD} the reference to the entity
*/
Model.get = function (config, success) {
var entity = new Model();
ApiProvider.get(modelName, config)
.success(function (data) {
angular.extend(entity, data);
if (success) {
success();
}
});
return entity;
};
/**
* Static method
* Gets entities of a model
* @param {Object} config @see ApiProvider config
* @return {CRUD[]} the reference to the entity
*/
Model.query = function (config, success) {
var entities = [];
ApiProvider.get(modelName, config)
.success(function (data) {
data.map(function (model) {
var entity = new Model();
angular.extend(entity, model);
return entity;
});
Array.prototype.push.apply(entities, data);
if (success) {
success();
}
});
return entities;
};
return Model;
},
// Direct link to ApiProvider.post method
post: ApiProvider.post,
// Direct link to ApiProvider.put method
put: ApiProvider.put
};
}]);
ApiProvider
.service('ApiProvider', function ($http) {
/**
* Private
* @param {string}
* @param {object}
* @return {string} Example: /service/[config.id[/config.relatedModel], /?config.params.key1=config.params.value1&config.params.key2=config.params.value2]
*/
var buildUrl = function (service, config) {
var push = Array.prototype.push,
url = [apiRoot, service],
params = [],
param = null;
// if a key id is defined, we want to target a specific resource
if ('id' in config) {
push.apply(url, ['/', config.id]);
// a related model might be defined for this specific resource
if ('relatedModel' in config) {
push.apply(url, ['/', config.relatedModel]);
}
}
// Build query string parameters
// Please note that you can use both an array or a string for each param
// Example as an array:
// {
// queryString: {
// fields: ['field1', 'field2']
// }
// }
// Example as a string
// {
// queryString: {
// fields: 'field1,field2'
// }
// }
if ('queryString' in config) {
// loop through each key in config.params
for (paramName in config.queryString) {
// this gives us something like [my_key]=[my_value]
// and we push that string in params array
push.call(params, [paramName, '=', config.queryString[paramName]].join(''));
}
// now that all params are in an array we glue it to separate them
// so that it looks like
// ?[my_first_key]=[my_first_value]&[my_second_key]=[my_second_value]
push.apply(url, ['?', params.join('&')]);
}
return url.join('');
},
request = function (method, url, methodSpecificArgs) {
trace({
method: method,
url: url,
methodSpecificArgs: methodSpecificArgs
}, 'ApiProvider request');
return $http[method].apply($http, [url].concat(methodSpecificArgs));
},
methods = {
'get': function (url, config) {
config.cache = false;
return request('get', url, [config]);
},
'post': function (url, data, config) {
config.cache = false;
return request('post', url, [data, config]);
},
'put': function (url, data, config) {
config.cache = false;
return request('put', url, [data, config]);
},
'delete': function (url, config) {
config.cache = false;
return request('delete', url, [config]);
}
};
return {
'get': function (service, config) {
config = config || {};
return methods.get(buildUrl(service, config), config);
},
'post': function (service, data, config) {
config = config || {};
return methods.post(buildUrl(service, config), data, config);
},
'put': function (service, data, config) {
config = config || {};
return methods.put(buildUrl(service, config), data, config);
},
'delete': function (service, config) {
config = config || {};
return methods.delete(buildUrl(service, config), config);
}
};
});
describe('CRUDServiceTest', function () {
'use strict';
var CRUDService;
beforeEach(function () {
inject(function ($injector) {
CRUDService = $injector.get('CRUDService');
});
});
it('should have a method build', function () {
expect(CRUDService).toHaveMethod('build');
});
it('should ensure that an instance of a built service has a correct value for getModelName method',
function () {
var expectedModelName = 'myService',
BuiltService = CRUDService.build(expectedModelName),
instanceOfBuiltService = new BuiltService();
expect(instanceOfBuiltService).toHaveMethod('getModelName');
expect(instanceOfBuiltService.getModelName()).toEqual(expectedModelName);
});
// TEST get
it('should ensure build returns a class with static method get', function () {
expect(CRUDService.build()).toHaveMethod('get');
});
it('should ensure get returns an instance of CRUD', function() {
var BuiltService = CRUDService.build(),
instanceOfBuiltService = new BuiltService();
expect((BuiltService.get()).constructor).toBe(instanceOfBuiltService.constructor);
});
// TEST query
it('should ensure build returns a class with static method query', function () {
expect(CRUDService.build()).toHaveMethod('query');
});
it('should a collection of CRUD', function () {
expect(CRUDService.build()).toHaveMethod('query');
});
it('should have a static method post', function () {
expect(CRUDService).toHaveMethod('post');
});
it('should have a static method put', function () {
expect(CRUDService).toHaveMethod('put');
});
});
TLDR;
In general I think it's a good idea to mock out your services. If you keep up on doing it, then it makes isolating the behavior of any service you add really easy.
That being said, you don't have to at all, you can simply use Jasmine spy's.
for instance if you were testing your CRUDService which had a method like this:
remove: function () {
return ApiProvider.delete(this.getModelName(), this.id);
}
You could, in your test write something like:
var spy = spyOn(ApiProvider, 'delete').andCallFake(function(model, id) {
var def = $q.defer();
$timeout(function() { def.resolve('something'); }, 1000)
return def.promise;
});
Then if you called it:
var promise = CRUDService.remove();
expect(ApiProvider.delete).toHaveBeenCalledWith(CRUDService.getModelName(), CRUDService.id);
So basically you can mock out the functionality you need in your test, without fully mocking out the service. You can read about it more here
Hope this helped!
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.