0

So I have a controller like this:

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

I am writing a test that will call the $scope.init() and has to verify that the products were filtered appropriately. I am mocking the $httpBackend so the code looks like this:

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

The problem is that without the setTimeout the test doesn't pass. Is there a way to test what I am trying to do without it and without introducing complex $q/promises just for the test? As a side note productService is returning a promise $http. Thanks.

Edit: setTimeout makes test run but no assertions are happening..

4
  • If $scope.init() returned a promise, this would be much easier. (and that only requires adding 7 characters to the function) Commented Aug 5, 2015 at 13:55
  • 1
    I believe it is $httpBackend.flush() that you want; execute it after the //Act section and the expectation will work without the timeout. Also note that the controller has already called scope.init(), so you don't need to call it again in the test. Commented Aug 5, 2015 at 13:59
  • @NikosParaskevopoulos that fixed it! Also with setTimeout the test was running but wasn't asserting anything. You can post a more detailed answer and I will accept it. In my original code I had $httpBackend.flush() after acting. Now I recreate the controller and call init just for this test. Thanks Commented Aug 5, 2015 at 14:02
  • @NikosParaskevopoulos so the mistake is that I had $httpBackend.flush() at the end, that I should have had before expect() . I forgot to add it in the question so I added it Commented Aug 5, 2015 at 14:09

1 Answer 1

1

Based on the comments, the problem was that the $httpBackend.flush() needs to be executed before the expectations. Effectively it resolves all requests it was trained to resolve (and their respective promises) so that expectations can run on completed promises.

It is also a good idea to use $httpBackend.verifyNoOutstandingExpectation() / $httpBackend.verifyNoOutstandingRequest() after the tests.

Also note that the controller has already called scope.init(), so calling it again in the test is redundant - and may even cause failures depending on the exact case.

Also for the setTimeout running but not asserting anything: Using it in a spec makes this spec asynchronous. You will have to define the spec with the done callback and call it from the asynchronous code, when the test is really finished, as:

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

Note again that Angular mocks provide ways to avoid using setTimeout for 99.9% of the time, even the $interval/$timeout services are properly mocked!

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.