Andrew Luhring
Andrew Luhring

Reputation: 1894

Angular - unit testing a controller that consumes a promise on initialization. ( Jasmine )

You have a controller ExampleController which consumes a method on a service RequestService.data during it's initialization, and you just want to test that the controllers (scope).property is set to a value in the response:

ExampleController:

app.controller('ExampleController'
            , ['$scope','RequestService', function($scope, RequestService){
  var ex = this;  
  (function initialize(){
    RequestService.
      data().
      then(function RSresp(info){
       ex.property = info.data.property;
      }
  })();
}]

RequestService:

app.service('RequestService', ['$http', function($http){
   this.data = function RSdata(){
     var closure = {prop: 'val', derp: 'aderp'};
     var url = 'http://aWebsite.' + closure.prop + '.com/' + closure.derp;
     return $http.get(url);
   }
}]);

Unit test:

describe('on initialization, ExampleController',function(){
 var controller, injector, $httpBackend, $scope, RequestService;

  beforeEach(function(){
   inject(function(_$injector_, _$controller_, _$rootScope_){
     injector = _$injector_;
     $scope = _$rootScope_.new();
     $httpBackend = injector.get('$httpBackend');
     controller = _$controller_('ExampleController', {$scope : $scope});
     RequestService = injector.get('RequestService');
   })
  })

  beforeEach(function(){
    $httpBackend.
       when('GET', 'http://aWebsite.val.com/aderp').
       respond({property: 'value'});
  });

  afterEach(function(){
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('has a property "property" with a value "value" ', function(){
    expect(controller.property).toBeDefined();
    expect(controller.property).toBe('value');
  });
});

I've tried a bunch of different things like:

expect($scope.property).toBeDefined();

or

spyOn(RequestService, 'data');
// or spyOn(RequestService, 'data').andCallThrough();
expect(controller.property).toBeDefined();
expect(RequestService.data).toHaveBeenCalled;

Karma's saying:

"Error: Unflushed requests"
or
on initialization, ExampleController has a property "property" with a value "value" Fail: undefined is not defined.

console.log(controller) doesn't show that the property is set, and it doesn't seem like RequestService is ever called...

I don't know. All of my tests for RequestService are passing, and the application itself works... but I can't figure out how to write the spec to reflect that.

I've looked at: Unit testing promises in controllers in AngularJS

and some others that haven't turned out to be irrelevant... but no luck

Upvotes: 3

Views: 1759

Answers (2)

Andrew Luhring
Andrew Luhring

Reputation: 1894

The solution to that problem was to call $digest.

Upvotes: 0

user2943490
user2943490

Reputation: 6940

Your tests currently try to do far too much.

The responsibility for testing your $http requests should be the tests for your RequestService. When testing anything that calls RequestService, you mock the RequestService.

In general, do not go any further than 1 dependency in unit tests (so, ExampleController -> RequestService would be your limit). Your integration tests will be responsible for testing the full sequence.

So your unit tests should look something like this:

describe('on initialization, ExampleController',function(){
 var $q, $controller, injector, $scope, RequestService;

  beforeEach(function(){
    inject(function(_$q_, _$injector_, _$controller_, _$rootScope_){
      $q = _$q_;
      injector = _$injector_;
      $controller = _$controller_;
      $scope = _$rootScope_.$new();
      RequestService = injector.get('RequestService');
    });
  });

  beforeEach(function(){
    spyOn(RequestService, 'data');
  });

  describe('with successful data retrieval', function () {
    beforeEach(function () {
      // mock a success
      RequestService.data.andReturn($q.when({property: 'value'}));
      controller = $controller('ExampleController', {$scope : $scope});

      $scope.$digest(); // fulfil the promises
    });

    it('has a property "property" with a value "value" ', function(){
      expect(controller.property).toBeDefined();
      expect(controller.property).toBe('value');
    });
  });

  describe('with failed data retrieval', function () {
    beforeEach(function () {
      // mock a fail
      RequestService.data.andReturn($q.reject(null));
      controller = $controller('ExampleController', {$scope : $scope});

      $scope.$digest(); // fulfill the promises
    });

    it('should do something', function(){
      expect('this').toBe('that');
    });
  });
});

Upvotes: 3

Related Questions