0

I have been learning how to test my app written in angular stuff and I'm getting confused because it seems there are a few things against each other. I try to follow John Papa's style guide, so that I'm using Sidewaffle templates for everything basically. At the moment, it seems, the suggested template and testability against each other.

The style guide says that there is an

activate()

method in your controller which takes care about the start-up logic of the controller as you can see here. However, general testing guide line says that private method should not be tested. I have the chance to test the result of the activate() method in the provided example, because it will be passed through

vm.avengers

variable. In his Pluralsight videos he uses common.activatecontroller() method where he uses

$q.all()

to combine promises, so that you can easily call many function in controller activation phase. Let say that I have a function which won't have a result can be passed through vm, for example a post message to WebApi to authenticate the user and get the token and set it up to headers, or something like this. Here is an example (service is not injected, it is just an example):

In the controller below the only business logic is that, when the controller is instantiated the activate() method calls the func1() method which calls the OAuthenticationService.authenticate() method. From testing perspective it is important whether the service was called or not. How to test this?

Here is a very similar question where one of the answers suggests that I should use this keyword like this:

this.activate()

But a comment says that it is not working with ControllerAs syntax. I'm following the ControllerAs syntax.

When I create a mock for the service with spy on the authenticate method the test says that I was not called, I think due to that the method is private.

I run out of ideas...

Thanks for any help in advance!

Example

(function () {
    'use strict';

    angular
        .module('app')
        .controller('controller', controller);

    controller.$inject = ['$location']; 

    function controller($location) {
        /* jshint validthis:true */
        var vm = this;
        vm.title = 'controller';

        activate();

        function activate() {

            func1();

        }

        function func1() {

            OAuthAuthenticateService.authenticate(user).then(function() {

                //setting up headers and other stuff, nothin will be part of $scope or vm;

            });

        }
    }
})();

Original code:

(function () {
    'use strict';

    var controllerId = 'requestAuthorizationController';

    angular
        .module('myapp')
        .controller(controllerId, requestAuthorizationController);

    requestAuthorizationController.$inject = ['$rootScope',
                                                '$scope',
                                                'requestAuthorizationService'
                                                'Restangular'];

    function requestAuthorizationController($rootScope,
                                            $scope,
                                            requestAuthorizationService
                                            Restangular) {
        /* jshint validthis:true */
        var vm = this;

//other business logic

        activate();

        function activate() {

            requestAuthorization();

        }

        function requestAuthorization() {

            vm.fired = undefined;

            requestAuthorizationService.getDummy();

        }
    }
})();

Jasmine test:

'use strict';

describe('RequestAuthenticationController Specification', function () {

    var RestangularProviderMock,
        localStorageServiceProvider,
        $httpProvider,
        requestAuthorizationController,
        requestAuthorizationServiceMock,
        $rootScope;

    //modules
    beforeEach(function() {

        angular.module('dilib.layout', []);
        angular.module('http-auth-interceptor', []);

    });


    //providers
    beforeEach(function () {

        module('dilib', function(RestangularProvider, _localStorageServiceProvider_, _$httpProvider_, $provide) {

            RestangularProviderMock = RestangularProvider;
            localStorageServiceProvider = _localStorageServiceProvider_;
            $httpProvider = _$httpProvider_;

            $provide.service('requestAuthorizationService', function() {
                this.getDummy = jasmine.createSpy('getDummy').and.callFake(function(num) {
                });
            });
        });

    });

    //to crank up the providers
    beforeEach(inject());

    beforeEach(inject(function (_$rootScope_, _$controller_, _requestAuthorizationService_) {

        $rootScope = _$rootScope_;
        requestAuthorizationController = _$controller_;

        requestAuthorizationServiceMock = _requestAuthorizationService_;

    }));



     describe('requestAuthorization function', function() {

         it('RequestAuthorizationService.getDummy() is called', function() {

            $rootScope.$digest();
            expect(requestAuthorizationServiceMock.getDummy).toHaveBeenCalled();

        });

    });


});
Community
  • 1
  • 1
AndrasCsanyi
  • 3,943
  • 8
  • 45
  • 77
  • Can you add your test code please? That will help a lot in diagnosing the problem. – Don Aug 10 '15 at 17:10
  • There is a mistake in [`activation example`](https://github.com/johnpapa/angular-styleguide#controller-activation-promises) you referenced. _Recommended_ version of `Avengers` should return result of `activate()` which it doesn't – Kirill Slatin Aug 10 '15 at 17:30
  • Do you mean `return activate()`? – AndrasCsanyi Aug 11 '15 at 09:57

2 Answers2

0

I think the whole approach I had is wrong. I have way too much business logic in the controller. According to this article and others I have to reduce the business logic in the controller as much as possible and place it in service public methods. If I follow this rule then there will be no need for private methods. However, the question still stands what to do with the

activate()

method. It is enough to test the result of the executed function which is the populated viewmodel? I think, the answer is yes.

At the moment I'm focusing on whether the viewmodel variables are populated properly or not, and I'm refactoring the business logic into services.

In my opinion it is very easy to write working but hard to test code, than working and easy to test code.

The another aspect of the story is that I have been working as a tester for 10 years with good experience at many aspects of testing. But now, I'm just scratching my head how to write code on testable / test driven development way. :)

Community
  • 1
  • 1
AndrasCsanyi
  • 3,943
  • 8
  • 45
  • 77
0

So in John Papa's style guide the activate() method is meant as an abstraction. If you have a lot of setup that you would be doing in your constructor method, instead abstract it out to the activate() method. Your constructor is then clean. I am not sure there is too much value to this in your posted code.

On a side note, you should only be unit testing the APIs, external interfaces, of your units. So for a controller, the properties and methods that are called by actions in views.

Martin
  • 15,820
  • 4
  • 47
  • 56