【问题标题】:How can I test data set in a service callback when testing a controller in AngularJS?在 AngularJS 中测试控制器时,如何在服务回调中测试数据集?
【发布时间】:2014-07-09 07:58:20
【问题描述】:

假设我有一个服务,它查询一些数据并将其设置在控制器中,有点类似于:

(Method on controller)

DogService.query(function(data)){
  if(data.isSuccess){
    $scope.IloveDogs = true;
    $scope.dogLovers += 1;
  }
})

这是高度简化的,但我将如何在我的控制器中测试调用模拟的dogService 时,它设置了正确的数据?

如果为了简单起见,我们说函数不是异步的并且处理承诺,我会创建一个模拟并将其注入控制器。模拟可能看起来像:

var DogService = {
  query: function(){
    return true;
  }
}

不幸的是,这不会运行 $scope.IloveDogs 设置为 true 并且 dogLovers 加一的代码。

任何想法,因为我不想将控制器中的代码从服务复制到模拟服务?

【问题讨论】:

    标签: angularjs unit-testing jasmine


    【解决方案1】:

    这就是我通常在单元测试中模拟服务的方式。 (你没有提到你使用的测试框架,所以我假设 Jasmine 是目前最流行的)。

    我只是创建了一个哑对象作为我的模拟对象,然后只是 Jasmine 的内置间谍功能来指示它返回的内容。请注意,这是 Jasmine 2.0 的语法。

    我使用$q 创建一个promise,并确保我能够从我的测试中引用它,以便解决它。

    describe('Spec', function() {
    
        var scope;
        var catServiceMock;
    
        var deferredCatCall;
    
        beforeEach(module('myModule'));
    
        beforeEach(inject(function($controller, $rootScope, $q) {
            scope = $rootScope;
    
            //Create a mock and spy on it to return a promise
            deferredCatCall = $q.defer();
            catServiceMock = {
                query: function() {}
            };
            spyOn(catServiceMock, 'query').and.returnValue(deferredCatCall.promise);
    
            //Inject the mock into the controller
            $controller('MyCtrl', {
                $scope: scope,
                catService: catServiceMock
            });
        }));
    
        it('proves that cats are better than dogs', function() {
            //resolve the promise that was returned by the mock
            deferredCatCall.resolve({
                isSuccess: true
            });
            //Need to trigger a $digest loop so angular process the resolved promise
            scope.$digest();
    
            //Check that the controller callback did something
            expect(scope.iLoveCats).toBeTruthy();
        });
    
    });
    

    对于不使用承诺的服务,我可能会这样做:

    describe('Spec', function() {
    
        var scope;
        var catServiceMock;
    
        beforeEach(module('myModule'));
    
        beforeEach(inject(function($controller, $rootScope, $q) {
            scope = $rootScope;
    
            //Create a mock and spy on it to return a value
            catServiceMock = {
                query: function() {}
            };
            spyOn(catServiceMock, 'query').and.returnValue({
                isSuccess: true
            });
    
            //Inject the mock into the controller
            $controller('MyCtrl', {
                $scope: scope,
                catService: catServiceMock
            });
        }));
    
        it('proves that cats are better than dogs', function() {
            //Check that the controller callback did something
            expect(scope.iLoveCats).toBeTruthy();
        });
    
    });
    

    这种方法的主要问题是您必须在实例化控制器之前规定服务将返回什么。这意味着,如果您想测试控制器对从服务接收到的不同数据的行为方式,您将不得不将多个 beforeEach 块嵌套在不同的 describe 块中,虽然它看起来不像样板文件在测试中你会得到更多。

    这就是为什么我更喜欢我的服务返回承诺的原因之一,即使它们不是异步的。

    【讨论】:

    • 您对 jasmin 的假设是正确的(请参阅标签 ;))。如果你实际上没有异步方法,你会怎么做呢?
    • 我现在也得看标签?! :) 我所有的服务都使用了 Promise,但我会试着想出一个例子来说明如何测试非 Promise 服务。
    • @Dofs 我已经编辑了我将为非承诺返回服务所做的事情。并解释了为什么我会让服务返回一个承诺,即使它不是异步操作。
    • 对于非承诺控制器(在这种情况下,资源包装在服务 serviceModule.factory('CatService', ['$resource', function($resource){ return $resource('/ cat/:id.json', {}, { 'query': {method: 'GET', isArray: true}} ); }]);) 它似乎没有进入回调(对于信息我不'不使用 jasmin 2.0,并使用 andReturn(...)。)
    • 我没有使用过角度资源,所以我不确定它到底是如何工作的。我什至不知道$resource 函数返回什么,所以我不确定我能帮上什么忙。我发布的 sn-p 只是我在一般情况下的做法,我不确定在将 $resource 引入混合时需要进行哪些修改。这并不是说不能做,只是我对那个模块了解不够。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 2023-04-01
    • 2013-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多