angularjs单位测试业力与茉莉花模拟困境

时间:2014-02-13 16:22:06

标签: javascript angularjs unit-testing mocking jasmine

我目前正在使用karmajasmine编写某些服务的测试,我想知道是否必须使用$http来模拟服务的服务依赖项,如下所述。 / p>

PS :我已经使用$httpBackend来模拟任何GET请求,如果我不模拟服务,我打算使用$httpBackend.expect* {{1} }


这是我正在测试的服务

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

最后,这是我到目前为止测试CRUDService的方式

.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);
        }
    };
});

TLDR;

根据$ http?

模拟或不模拟依赖服务

1 个答案:

答案 0 :(得分:5)

总的来说,我认为嘲笑你的服务是个好主意。如果你继续这样做,那么它就可以很容易地隔离你添加的任何服务的行为。

话虽如此,你根本不需要,你可以简单地使用Jasmine间谍。

例如,如果您正在测试具有以下方法的CRUDService:

remove: function () {
    return ApiProvider.delete(this.getModelName(), this.id);
}

你可以在你的测试中写下类似的东西:

var spy = spyOn(ApiProvider, 'delete').andCallFake(function(model, id) {
    var def = $q.defer();
    $timeout(function() { def.resolve('something'); }, 1000)
    return def.promise;
});

然后如果你打电话:

var promise = CRUDService.remove();
expect(ApiProvider.delete).toHaveBeenCalledWith(CRUDService.getModelName(), CRUDService.id);

基本上,您可以在测试中模拟出所需的功能,而无需完全模拟服务。您可以阅读更多here

希望这有帮助!