问题 如何在AngularJS中模拟包含资源的服务


我有一个包含这样的资源工厂的服务:

serviceModule.factory('ProjectResource', ['$resource', function($resource){
    return $resource('/projects/:id.json', {}, {
        'query': {method: 'GET', isArray: true}}
    );
}]);

在一个驻留在控制器中的表单中,我注入了serviceModule,并创建了一个资源对象的新实例:

  $scope.project = new ProjectResource({name: 'Enter a name'})

我有一些嘲弄它的问题。我尝试创建这样的模拟对象,并将其注入控制器:

 mockProjectResource = {
            query: function(){
                deferred = $q.defer();
                deferred.resolve({id: 1, :name:'test'});
                return deferred.promise;
            }
        };

无论单元测试,我都会收到错误:

TypeError: undefined is not a function

哪个指向Project Resource对象的初始化($scope.project = new ProjectResource({name: 'Enter a name'}))。

有没有什么好方法来嘲笑 new ProjectResource(...)


1712
2018-06-24 08:24


起源



答案:


从我所看到的,我只需要创建一个存根对象而不是一个模拟对象。

ProjectResource 是一个 构造函数 功能。您可以像这样创建它的存根:

mockProjectResource = function (properties){
    for(var k in properties) 
       this[k]=properties[k];
};

mockProjectResource.query = function(){
      deferred = $q.defer();
      deferred.resolve({id: 1, :name:'test'});
      return deferred.promise;
};

如果您需要知道模拟和存根之间的差异: Rhino Mocks上的模拟和存根之间有什么区别?。 (不要认为这个链接仅适用于Rhino mock,这个概念对所有框架和技术都是一样的)


6
2018-06-28 02:09



好吧,我不知道Angular是否做了一些魔术,但是构造函数的简单存根工作了。谢谢你的链接。 - Dofs
mockProjectResource.query的更短实现是 return $q.when({id: 1, name:'test'}) - Mihnea Simian


答案:


从我所看到的,我只需要创建一个存根对象而不是一个模拟对象。

ProjectResource 是一个 构造函数 功能。您可以像这样创建它的存根:

mockProjectResource = function (properties){
    for(var k in properties) 
       this[k]=properties[k];
};

mockProjectResource.query = function(){
      deferred = $q.defer();
      deferred.resolve({id: 1, :name:'test'});
      return deferred.promise;
};

如果您需要知道模拟和存根之间的差异: Rhino Mocks上的模拟和存根之间有什么区别?。 (不要认为这个链接仅适用于Rhino mock,这个概念对所有框架和技术都是一样的)


6
2018-06-28 02:09



好吧,我不知道Angular是否做了一些魔术,但是构造函数的简单存根工作了。谢谢你的链接。 - Dofs
mockProjectResource.query的更短实现是 return $q.when({id: 1, name:'test'}) - Mihnea Simian


你看过了吗? 'ngMock'

angular.module('project-resource', ['ngResource'])
    .factory('ProjectResource', function($resource){
        //Do not need to redefine the `query` action as it is defined by default by ngResource
        return $resource('/projects/:id.json', {'id': '@id'});
    });

describe('ProjectResource', function(){
    beforeEach(module('service-module-name'));

    afterEach(inject(function($httpBackend){
        //These two calls will make sure that at the end of the test, all expected http calls were made
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    }));

    it('mock http call', inject(function($httpBackend, ProjectResource) {
        var resource = new ProjectResource({
            id : 'abcde'
        });
        //Create an expectation for the correct url, and respond with a mock object
        $httpBackend.expectGET('/projects/abcde.json').respond(200, JSON.stringify({
            id : 'abcde',
            name : 'test'
        }));

        //Make the query
        resource.$query();

        //Because we're mocking an async action, ngMock provides a method for us to explicitly flush the request
        $httpBackend.flush();

        //Now the resource should behave as expected
        expect(resource.name).toBe('test');
    }));
});

8
2018-06-24 10:47



我没有看到你在控制器中注入了一个模拟器?我的意思是,你不做类似的事情ctrl = $ controller('projectsCntl',{$ scope:scope,$ http:$ http,ProjectResource:mockProjectResource})。这是在控制器中注入模拟服务的错误方法吗? - Dofs
我误解了你的问题,我认为你想要一种模拟AJAX调用来测试你的问题的方法 ProjectResource 类。我将更新我的答案,我喜欢注入模拟的依赖项。 - Clark Pan


我创建了一个小型库来帮助模拟ngResource: https://github.com/michalstawski/ngResourceMock

它与$ httpBackend具有类似的api。有了它你可以在你的测试中做这样的事情:

$scope.project.whenQuery().resolve([]);

这将解析每个使用空数组查询的调用。

它比自己模拟资源更容易,更快,同时在比httpBackend更高的抽象级别上。


1
2018-03-07 16:40





模拟基于资源的服务的另一种方法是这样的:

// the name of an object should start with a capital letter
// in the case where we create a function object (as opposed to
// a literal object).
MockProjectResource = function(){};

// we add the "query" method to the "prototype" property
MockProjectResource.prototype.query = function(){
  deferred = $q.defer();
  deferred.resolve({id: 1, :name:'test'});
  return deferred.promise;
};

模拟上面的查询函数适用于控制器期望通过调用返回promise的情况,例如:

var projectController = angular.module('projectController', []);
projectController.controller('ProjectController', ['$scope', 'ProjectResource',
  function($scope, ProjectResource) {

    var promise = ProjectResource.query();
    promise.then(function(data) {
        console.log(data);
      },
      function(err){
        console.log(err);
      });

}]);

但是,如果控制器期望在回调中发送结果,如下所示:

      ProjectResource.query(function(data) {
          console.log(data);
        },
        function(err){
          console.log(err);
        });

那么,服务需要像这样嘲笑:

MockProjectResource.prototype.query = function(success, error){
  var deferred = q.defer();
  var promise = deferred.promise;
  promise.then(success, error);
  deferred.resolve({id: 1, :name:'test'});
};    

有关完整示例,请参阅我对类似问题的回答:

你如何模拟angularjs $资源工厂


0
2017-10-28 17:05



要查看函数对象和对象文字之间的区别,请查看Douglas Crockford撰写的“Javascript:The good parts”一书,第20-30页。通过Google搜索可以轻松找到该书的(免费)PDF版本。 - claudius