unit test spy on $emit

All we need is an easy explanation of the problem, so here it is.

I’m trying to spy on an $emit from a directive, but somehow I cannot get the spy to ‘hear’ the $emit.

This is the code in my directives’ controller:

$scope.$on('send', function () {
    console.log('called');
    $scope.$emit('resultSend', {'ok': true, 'data': ''});
});

This is my unit test:

var $rootScope, $compile, elm, element;

beforeEach(inject(function ($injector) {
    $rootScope = $injector.get('$rootScope');
    $compile = $injector.get('$compile');
    elm = angular.element('<test></test>');
    element = $compile(elm)($rootScope);
}));


it('should listen for the send broadcast and emit the resultSend', function () {
    spyOn($rootScope, '$emit');
    $rootScope.$broadcast('send');
    expect($rootScope.$emit).toHaveBeenCalledWith('resultSend');
});

The console.log output (‘called’) is printed out by Karma, so I guess the unit test broadcast event does work.

Does this have to do with $emit not broadcasting down but up, and if so, how do I catch it, and if not, how else do I handle this case?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

According to the docs here, you are correct in your understanding of the difference between $emit and $broadcast. However, I think the problem is in your use of $scope and $rootScope. Your $rootScope will be at the top level of your scope hierarchy. I’m guessing (just by looking at your snippets without being able to see all the code) that your $scope in your controller is a nested controller, meaning that $scope in your controller is a child of the app’s $rootScope.

Because of that, when your unit test spys on the $rootScope.$emit function, it is not actually spying on your controller’s $scope.$emit() call. Those two “scopes” are different, not the same thing. So, you need to mock the $scope that you provide for the controller and then do a spyOn on that.

For example, in your beforeEach:

var ctrl, scope;

beforeEach(function() {
    module('<INSERT YOUR CONTROLLERS MODULE NAME HERE>'); 
    inject(function($rootScope, $controller) {
        scope = $rootScope.$new();
        ctrl = $controller('<CTRL NAME HERE>', {$scope: scope});
    });
});

This code will actually create a “mock” scope variable and will provide that object to your controller, which you can then do spies and other things with. Such as:

spyOn(scope, '$emit');
// do whatever triggers the "$emit" call
expect(scope.$emit).toHaveBeenCalledWith('resultSend');

I’m pretty sure that should fix your problem. Let me know if that needs more explanation.

Method 2

If your directive has a controller, you could, and should, test that separately from the directive. That’s the whole point of an MVC architecture, you can test the C seperately from the V. 😉

That said, it would be a plain-Jane controller test spec.

Another tip: You should do all of your set up in your beforeEach() block (i.e. spies and whatever) and then do the assertions in your it() blocks.

Finally: Make sure that the spy you’re setting up, is on the scope you’re passing into the controller you’re testing.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply