vendredi 29 avril 2016

Manage angularjs controller init with karma tests

Ive seen such questions and they all say 'extract logic out into a service and mock the service'. Simple, except i have as much as possible.

So when the controller inits i have a call to $scope.getWidgets() this method calls widgetService to get a list of widgets for the user, it will then display a toast notification using notifyService. Should widgetService reject its promise or the user have no widgets a call to notifyService is made. This is all done when the controller inits.

$scope.getWidgets = function () {
    widgetService.getWidgets()
        .then(function (widgets) {
            if (widgets.length === 0) {
                notifyService.notify('no widgets');
            }

            $scope.widgets = widgets;
        })
        .catch(function (e) {
            notifyService.notify('oh noes');
        });
}

//called at bottom of controller
$scope.getWidgets();

Now all my tests so for have not had need to run any digest cycles, run running promises etc. The method im trying to test calls a service to save a widget. Should it succeed or fail it will again call notifyService to send a notification to the user. I'm testing that the notification is triggered using karma's spyOn and also testing some flags are set correctly. There's no real logic, thats done in the service.

$scope.saveWidget = function (widget) {
    widgetService.saveWidget(widget)
        .then(function () {
            notifyService.notify('all dandy');
            //set some scope flags, no logic
        })
        .catch(function (e) {
            notifyService.notify('oh noes');
            //set some more scope flags, no logic
        });
}

So now i have have to run the digest cycle to trigger my promises. The Trouble is my mock for widgetService.getWidgets() returns a promise. So when i run the digest first it resolves the promise registered in my init, which calls the notify service, which activates my spyon, concluding my test with false data.

Here are my tests, (please forgive the big code block, ive tried to strip it down as much as possible).

describe('tests', function () {
    var scope, controlelr, _mockWidgetService, _mockNotifyService;

    beforeEach(function () {
        //Init the service mocks, angular.noop for methods etc
        _mockWidgetService = init();
        _mockNotifyService = init();
    });

    beforeEach(function () {
        //Regisetr hooks to pull in my mocked services
        angular.mock.module('myModule');

        angular.mock.module(function($provide) {
            $provide.value('widgetService', _mockWidgetService);
            $provide.value('notifyService', _mockNotifyService);
        })
    });

    angular.mock.inject(function ($controller, $rootScope, widgetService, notifyService) {
        //create scope and inject services to create controller
        scope = $rootScope.$new();

        //Trouble is $scope.getWidgets() is called now.
        controller = $controller('testController', {
            $scope: scope,
            widgetService: widgetService,
            notifyService: notifyService
        });
    });

    describe('getWidgets', function() {
        it('myTest', function () {
            //I cannow manipulate mock services moreso if needed just for this test
            spyOn(_mockNotifyService, 'notfy');

            //Call save widget
            scope.saveWidget();

            scope.$digest();

            expect(_mockNotifyService.notify).toHaveBeenCalledWith(//values);
        });
    });
});

So as you can see im struggling, i could mock all the mocks required for the init code, but id rather only mock the services i need for that test. Plus i don't like the idea of the init code running every single test potentially causing false errors (it has its own tests).

Ive seen $onInit event the controller calls, i had the idea of putting the init call in that but i don't see how i can intercept that to prevent it running for the tests that dont need it. Any advise or solutions to such use cases would greatly appreciated.

Aucun commentaire:

Enregistrer un commentaire