Unit testing $provide.decorator("$exceptionHandler") is this possible?

1,666 views
Skip to first unread message

James Morgan

unread,
Mar 18, 2014, 6:00:34 AM3/18/14
to ang...@googlegroups.com
Hi I have written a fairly trivial decorator around the '$exceptionHandler' service so I can post any failures to a http endpoint. This seems to be work fine and I'm pleased with its simplicity.

I am however having trouble testing this aspect of my application in jasmine. I have tried loading my whole app in the test i.e. 'myApp' but this to just hang the jasmine runner and crash PhantomJS & Chrome when running. 

Any ideas how I can test this code, ideally in isolation?

Thanks for any tips,

James

The provider:

###
 * Decorate the `$exceptionHandler` providing functionality for logging
 ###
app.config(($provide) ->
    $provide.decorator("$exceptionHandler", ($delegate, ErrorLoggingService) ->
        (exception, cause) ->

            # Delegate on the original `$exceptionHandler` i.e. $log.error()
            $delegate(exception, cause)

            # Record the error server side
            ErrorLoggingService.logError(exception, cause)
    )
)


The test so far:

describe('$exceptionHandler', function() {

    beforeEach(module('myApp.exceptionHandler'));

    function TestOnlyCtrl($exceptionHandler) {
        $exceptionHandler("Some Random Error")
    }

    // Run test
    describe('$exceptionHandler delegation', function(){

        var $log, $httpBackend, ErrorLoggingService;

        var exception = {
            message: "Error Message",
            stack: "Error Stack"
        };
        var cause = "Some Cause";

        beforeEach(module(function($exceptionHandlerProvider) {
            $exceptionHandlerProvider.mode("log");
        }));

        beforeEach(inject(function($injector, $controller) {
            $log = $injector.get('$log');
            $httpBackend = $injector.get('$httpBackend');
            $exceptionHandler = $injector.get('$exceptionHandler');

            $controller(TestOnlyCtrl);

            ErrorLoggingService = $injector.get('ErrorLoggingService');
        }));

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

        it('Should record and delegate on to ErrorLoggingService', function() {

            // I Should be able to do my asserts on the delegated service here?
            expect($exceptionHandler.errors[0]).toEqual("Some Random Error");
            
        });
    });
});

James Morgan

unread,
Mar 19, 2014, 4:39:31 AM3/19/14
to ang...@googlegroups.com
Incase anyone else finds this and has the same problem this is how i solved it. 

Firstly I moved more $exceptionHandler decorator into its own module so I could test it in isolation. (See below in coffeescript)

@errorhandler = angular.module('myApp.exceptionHandler', [])

###
* Decorate the `$exceptionHandler` providing functionality for logging
###
errorhandler.config(($provide) ->
    $provide.decorator("$exceptionHandler", ($delegate, ErrorLoggingService) ->
        ###
         * @param {Error} exception Exception associated with the error.
         * @param {string} cause optional information about the context in which the error was thrown.
        ###
        (exception, cause) ->

            # Delegate on the original `$exceptionHandler` i.e. $log.error()
            $delegate(exception, cause)

            # Record the error server side
            ErrorLoggingService.logError(exception, cause)
    )
)

Secondly this is my test which seems to work as expected, i.e. I spyOn my service and ensure it invoked when the exception handler is called:

    beforeEach(module('myApp.exceptionHandler'));

    // Setup an test controller
    function TestOnlyCtrl($exceptionHandler) {

        // Trigger a reported error
        $exceptionHandler("Some Random Error")
    }

    describe('$exceptionHandler delegation', function(){

        var $httpBackend, $controller, ErrorLoggingService;

        beforeEach(module(function($exceptionHandlerProvider) {
            $exceptionHandlerProvider.mode("log");
        }));

        beforeEach(inject(function($injector) {
            $httpBackend = $injector.get('$httpBackend');

            ErrorLoggingService = $injector.get('ErrorLoggingService');
            spyOn(ErrorLoggingService, "logError").andCallThrough();
        }));

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

        it('Should delegate on to ErrorLoggingService when error found', inject(function($controller) {
            $httpBackend.expectPOST('/error/log').respond(200);

            // Initiate the controller -> this will trigger an error
            $controller(TestOnlyCtrl)

            $httpBackend.flush();

            expect(ErrorLoggingService.logError).toHaveBeenCalledWith('Some Random Error', undefined);
        }));
    });

Hope it helps, the main problem I had is not realise that you place the $exceptionHandler decorator inside its own module which was preventing me from booting the full app for my jasmine test.

Hope it helps.

Samuel Durand

unread,
Jan 8, 2015, 4:04:54 PM1/8/15
to ang...@googlegroups.com
Hi @james, thanks for this code , very useful. The secret was indeed to put the handler in a separate module.
In my case I have some trouble applying this technique because I am using some dependencies that are included in the main module, as well as some constants. Without including the main module in the tests I can't inject those elements in the unit test, but with this main module and the one for the handler it does not work ...

Any idea ?

Samuel Durand

unread,
Jan 8, 2015, 4:43:49 PM1/8/15
to ang...@googlegroups.com
I found a way, by using $provide to provide some mock services and constants, in my cas it is ngToast and config (contains the constants in the real app). Hope it can help someone

describe('Unit: Exception handler', function() {

var $exceptionHandler, logger, $rootScope;

//html templates
beforeEach(module('templates'));

//load main module and mock translations
beforeEach(module('app.common', function($provide, $exceptionHandlerProvider) {

$exceptionHandlerProvider.mode("log");

$provide.factory('ngToast', function () {
return {
create: function() {}
};
});

$provide.constant('config', {});

}));

beforeEach(inject(function (_$rootScope_, _$exceptionHandler_, _logger_) {

$rootScope = _$rootScope_;
$exceptionHandler = _$exceptionHandler_;

logger = _logger_;
spyOn(logger, 'logError');

}));

it('init the data page', function() {

$exceptionHandler("Some Random Error");

expect(logger.logError).toHaveBeenCalled();
});

});

Le mardi 18 mars 2014 10:00:34 UTC, James Morgan a écrit :
Reply all
Reply to author
Forward
0 new messages