在AngularJS中测试控制器时,如何测试服务回调中的数据集?

时间:2014-07-09 07:58:21

标签: angularjs unit-testing jasmine

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

(Method on controller)

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

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

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

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

遗憾的是,这不会运行$scope.IloveDogs设置为true的代码,而dogLovers会增加1。

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

1 个答案:

答案 0 :(得分:0)

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

我只是创建一个愚蠢的对象来充当我的模拟,然后只是Jasmine的内置间谍功能来决定它返回的内容。请注意,这是Jasmine 2.0的语法。

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

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

});

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

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块中,并且看起来一目了然比如它在测试中的样板较少,你最终会得到更多。

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