0

I have a problem with the following situation:

I have a Controller that does a service call to get the different available languages and their greetings. And I would like to test this controller, I have based the test I've written from the following sites and articles:

http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html#testing-controllers

Angular unit-test controllers - mocking service inside controller

http://jasmine.github.io/2.2/introduction.html

and ofcourse AngularJs documentations

But I have a feeling that i am doing some things wrong or overdoing my tests.

In the ones i have writted, the first 3 pass, but the 4th one (in my eyes the most important one) fail.

Could someone be so kind to help me out or point me in the right direction. It seems like every article I read says something different on what and how to test.

Controller

angular.module('app')
.controller('MainCtrl', function ($scope, LanguagesService) {
    $scope.languages = LanguagesService.getAll();
});

Service

angular.module('app')
  .factory('LanguagesService', function () {
   var lang = {};

   lang.greetings = [
      'Welkom bij,',
      'Bienvenu chez'
   ];

   lang.languages = [
     {
       name: 'Nederlands',
       code: 'nl'
     },
     {
       name: 'Français',
       code: 'fr'
     }
   ];

   return {
     getAll: function () {
        return lang;
     }
   };

});

My unit test for the controller

describe('Controller: MainCtrl', function () {

// load the controller's module
beforeEach(module('app'));

var MainCtrl,
      scope,
      LanguagesService;

// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, _LanguagesService_) {
  scope = $rootScope.$new();
  LanguagesService = _LanguagesService_;
  MainCtrl = $controller('MainCtrl', {
    $scope: scope,
    'LanguagesService': LanguagesService
  });

  /*
  * Spy on service
  */
  spyOn(LanguagesService, 'getAll');
}));

/*
* Test 1: Is this test overkill ? As the tests wont run if the service is not injected
*/
it('should get an instance of LanguagesService', function() {
  expect(LanguagesService).toBeDefined();
});

it('should attach languages to the scope', function() {
  expect(scope.languages).not.toBe(null);
});

it('should have the same amount of languages as greetings', function() {
  expect(scope.languages.languages.length).toBe(scope.languages.greetings.length);
});

/*
* Test 4: This test fails
*/
it('should have called LanguagesService method getAll', function() {
  expect(LanguagesService.getAll).toHaveBeenCalled();
});

});
Community
  • 1
  • 1
RVandersteen
  • 2,067
  • 2
  • 25
  • 46
  • What is the problem with your tests? Are you seeing an error? In order to use `toHaveBeenCalled` the object under test needs to be a spy. – Davin Tryon Mar 06 '15 at 18:52
  • I've put a spy in the beforeEach (spyOn(LanguagesService, 'getAll');), Or should i put this in another location in order for it to work ? – RVandersteen Mar 06 '15 at 18:56
  • I see now. No, that should work. What is the reason that jasmine gives that the test is failing? – Davin Tryon Mar 06 '15 at 19:03
  • PhantomJS 1.9.8 (Linux) Controller: MainCtrl should have called LanguagesService method getAll FAILED Expected spy getAll to have been called. at /test/spec/controllers/main.js:36 – RVandersteen Mar 06 '15 at 19:05

2 Answers2

3
describe('Controller: MainCtrl', function () {

    // load the controller's module
    beforeEach(module('app'));

    var MainCtrl,
      scope,
      LanguagesService;

    var createController;
    var spy;

    // Initialize the controller and a mock scope
    beforeEach(inject(function ($controller, $rootScope, _LanguagesService_) {
        scope = $rootScope.$new();
        LanguagesService = _LanguagesService_;
        MainCtrl = $controller('MainCtrl', {
            $scope: scope,
            'LanguagesService': LanguagesService
        });

        createController = function () {
            return $controller('MainCtrl', {
                '$scope': scope,
                'LanguagesService': LanguagesService
            });
        };

        /*
        * Spy on service
        */
        spy = spyOn(LanguagesService, 'getAll');
    }));

    /*
    * Test 1: Is this test overkill ? As the tests wont run if the service is not injected
    */
    it('should get an instance of LanguagesService', function () {
        expect(LanguagesService).toBeDefined();
    });

    it('should attach languages to the scope', function () {
        expect(scope.languages).not.toBe(null);
    });

    it('should have the same amount of languages as greetings', function () {
        expect(scope.languages.languages.length).toBe(scope.languages.greetings.length);
    });

    /*
    * Test 4: This test fails
    */
    it('should have called LanguagesService method getAll', function () {
        createController();
        expect(spy).toHaveBeenCalled();
    });

});
j08691
  • 204,283
  • 31
  • 260
  • 272
Ralf D'hooge
  • 262
  • 3
  • 7
  • In the beforeEach method I defined a function createController, so if you call this method from the test, it will be runned and the the getAll() function will be called. – Ralf D'hooge Mar 15 '15 at 19:48
1

For future reference, this is how i solved my problem:

The first test I dropped, it really seemed like overkill.

The 3rd test I dropped as well, as it tests the output of the service and not the behavior of the controller, I moved this test to my Services Unit test.

I managed to make the 4th test work by mocking the Service in my test:

'use strict';

describe('controller: MainCtrl', function() {
  var ctrl, LanguagesService, $scope;

  beforeEach(module('fitApp'));

  beforeEach(inject(function($rootScope, $controller) {
    LanguagesService = {
      getAll: function() {}
    };

    spyOn(LanguagesService, 'getAll').and.returnValue('Foo');

    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl', {$scope: $scope , LanguagesService: LanguagesService });
  }));

  it('should call LanguagesService.getAll() once', function() {
    expect(LanguagesService.getAll).toHaveBeenCalled();
    expect(LanguagesService.getAll.calls.count()).toEqual(1);
  });

  it('should attach languages to the scope', function() {
    expect($scope.languages).toEqual('Foo');
  });

});

Both tests pass and test the behavior of the controller.

I hope someone can confirm to me that this was the right choice

RVandersteen
  • 2,067
  • 2
  • 25
  • 46