如何在不使用Timeout的情况下测试angularjs控制器异步行为

时间:2015-08-05 13:53:50

标签: angularjs unit-testing karma-jasmine angularjs-controller

所以我有一个像这样的控制器:

angular.module('someModule').controller('someController',function(productService) {
   $scope.products = [];
   $scope.init = function() {
      aService.fetchAll().then(function(payload) {
          $scope.products = filterProducts(payload.data);
      });
   }
   $scope.init();

   function filterProducts(products) {
      //for each of products filter some specific ones
      return filteredProducts;
   }
});

我正在编写一个将调用$ scope.init()的测试,并且必须验证产品是否已正确过滤。我在嘲笑$ httpBackend所以代码看起来像这样:

describe("someController", function() {
    "use strict";
    var $controller; //factory
    var controller; //controller
    var $rootScope;
    var $state;
    var $stateParams;
    var $injector;
    var $scope;
    var $httpBackend;
    var productService;

    beforeEach(function(){
        angular.mock.module("someModule")


        inject(function (_$rootScope_, _$state_, _$injector_, $templateCache, _$controller_, _$stateParams_, _$httpBackend_, _productService_) {
            $rootScope = _$rootScope_;
            $state = _$state_;
            $stateParams = _$stateParams_;
            $injector = _$injector_;
            $controller = _$controller_;
            $httpBackend = _$httpBackend_;
            productService = _productService_;
        });
        controller = $controller("someController", {$scope: $scope, $state: $state});
    });


    it("init() should filter products correctly",function(){
        //Arrange
        var expectedFilteredProducts = ["1","2"];
        var products = ["0","1","2"];
        $httpBackend.whenGET("api/products").respond(products);

        //Act
        $scope.init();

        //Assert
        setTimeout(100,function(){
            expect($scope.products).toEqual(expectedFilteredProducts);
        });

        $httpBackend.flush();
    });
});

问题是如果没有setTimeout,测试就不会通过。有没有办法测试我在没有它的情况下尝试做什么,而且没有为测试引入复杂的$ q / promises?作为旁注,productService返回一个promise $ http。感谢。

编辑:setTimeout使测试运行但没有发生断言..

1 个答案:

答案 0 :(得分:1)

根据评论,问题是$httpBackend.flush()需要在预期之前执行。实际上,它解决了所有被解决的请求(及其各自的承诺),以便期望可以在已完成的承诺上运行。

在测试后使用$httpBackend.verifyNoOutstandingExpectation() / $httpBackend.verifyNoOutstandingRequest()也是一个好主意。

另请注意,控制器已调用scope.init(),因此在测试中再次调用它是多余的 - 甚至可能会导致失败,具体取决于具体情况。

同样对于setTimeout正在运行但没有声明任何内容:在规范中使用它会使此规范异步。您必须使用done回调定义规范,并在测试真正完成时从异步代码中调用它,如下所示:

it("init() should filter products correctly",function(done) {
    ...
    //Assert
    setTimeout(100, function() {
        ...
        done();
    });
});

再次注意,Angular模拟提供了避免在99.9%的时间内使用setTimeout的方法,即使$interval / $timeout服务已被正确模拟!