Mocking the event object in AngularJS event unit testing

10,754

Solution 1

In this line:

$rs.$broadcast('$locationChangeStart', eventStub);

you provide an argument that will be transmittet alongside with the event, not the event itself. That's why you will get here:

$rootScope.$on('$locationChangeStart', function (event) 

2 objects as arguments. The full signature for your event should be:

$rootScope.$on('$locationChangeStart', function (event, eventStub) 

So: you can't test the call of stopPropagation in the way you have tried it.

If you have a look at the angular src (line 12185...) you will see, that the event is created without any possibility to mock this object. And the $scope itself is not mocked by angular-mock.

If one want to test that preventDefault is called, i would write a service that has a function that calls preventDefault. This service can easily be spyed on.

Solution 2

$scope.$broadcast returns the Event object, so you can do this:

var event = $scope.$broadcast("someEvent");
expect(event.defaultPrevented).toBeTruthy();
Share:
10,754
Nicolás Straub
Author by

Nicolás Straub

I am a computer nerd. I learned programming when I was 6, started working when I was 16, and haven't stopped since. My specialties are the user experience, domain architecture, tooling, and refactoring. I have served as architect and manager for multiple projects, leading teams of up to 5 people. I firmly believe in learning the underlying theory when it comes to software development, so instead of reading "teach yourself PHP in 2 weeks" or "Python in 24 hours" I prefer "Design Patterns", "Refactoring", "Analysis Patterns", "Domain-Driven Design" and other books which tell stories of the underlying patterns, paradigms and practices that compose software development. This gives me a perspective which allows me to pick up any language and start working with it in days. With that said, being a C# and JavaScript expert, I have read the most technical books on those subjects and have in-depth knowledge of how JS, C# and the .Net CLR work. I value TDD and SOLID practices, but take a pragmatic approach to them. So, you wont find me writing fifty-four unit tests for 3 lines of code or a hundred 3-line files, for the sake of not violating the Single Responsibility Principle. When Im not working Im either learning more about software development or playing with my 8-year old. Hes starting to take an interest in programming so maybe these two worlds will collide. for now, the joy of being a loving father to him is what keeps me going.

Updated on July 21, 2022

Comments

  • Nicolás Straub
    Nicolás Straub almost 2 years

    I have the following test:

        it('Should keep location when user rejects confirmation', inject(function ($controller, $rootScope) {
            var confirmStub = sinon.stub(),
                eventStub = {
                    preventDefault: sinon.spy()
                };
    
            miscServiceStub = function () {
                this.confirm = confirmStub;
            };
    
            confirmStub.returns(false);
    
            initializeController($controller, 'Builder', $rootScope);
    
            $rs.$broadcast('$locationChangeStart', eventStub);
            expect(confirmStub).toHaveBeenCalledOnce();
            expect(confirmStub).toHaveBeenCalledWith('Are you sure you want to leave? you will loose any unsaved changes.');
            expect(eventStub.stopPropagation).toHaveBeenCalledOnce();
    
            miscServiceStub = function () {};
        }));
    

    which tests this code:

    $rootScope.$on('$locationChangeStart', function (event) {
        dump(arguments);
        if (!$miscUtils.confirm('Are you sure you want to leave? you will loose any unsaved changes.')){
            event.preventDefault();
        }
    });
    

    event.$stopPropagation doesn't call the mock event, and dump(arguments) shows that it is being passed into the event right after the real event object:

    Chromium 31.0.1650 (Ubuntu) DUMP: Object{
        0: Object{name: '$locationChangeStart', targetScope: Scope{$id: ..., $$childTail: ..., $$childHead: ..., $$prevSibling: ..., $$nextSibling: ..., $$watchers: ..., $parent: ..., $$phase: ..., $root: ..., this: ..., $$destroyed: ..., $$asyncQueue: ..., $$postDigestQueue: ..., $$listeners: ..., $$isolateBindings: ..., activeTab: ..., routeParams: ...}, preventDefault: function () { ... }, defaultPrevented: false, currentScope: Scope{$id: ..., $$childTail: ..., $$childHead: ..., $$prevSibling: ..., $$nextSibling: ..., $$watchers: ..., $parent: ..., $$phase: ..., $root: ..., this: ..., $$destroyed: ..., $$asyncQueue: ..., $$postDigestQueue: ..., $$listeners: ..., $$isolateBindings: ..., activeTab: ..., routeParams: ...}}, 
        1: Object{stopPropagation: spy}
    }
    

    how can I make it so the event object is the mock event and not the real event object itself? Am I approaching this the right way? I'm quite new to Angular and any comments on the code/test would be greatly appreciated.

    If you need any more related code please tell me.