Tuesday, May 24, 2016

Testing $mdSidenav

I'm working with Angular Material on my current project and it's been interesting.  I'm writing unit tests to try to cover every line, branch, etc. (at least every reasonable combination) and I came across a situation where I needed to confirm that $mdSidenav was being called properly.  I'm using Jasmine and Karma and I couldn't get it to work.

Fortunately, I wasn't the only person who ran into this problem and I found part of my answer here.  That allowed me to verify that $mdSidenav() was called, but not what value was passed to it.  I had to add one more little piece and I was good to go.  Here's what I did.

My controller function:
$scope.openSidenav = function() {
    $mdSidenav('menu').toggle();
};

And my test:
it('openSidenav should pass \'menu\' to $mdSidenav.toggle', function() {
    $controller('toolbarController', { $scope: scope, hotkeys: hotkeys });
 
    scope.openSidenav();
 
    expect(sideNavToggleMock).toHaveBeenCalled();
    expect(passedSideNavId).toBe('menu');
});

But the most important part is in the setup of the tests. After I create the module (like this:
beforeEach(module('quotelite'));) I have to create a spy assigned to a global variable, then use the $provide service to register a factory with the name $mdSidenav and set it to... you know what? Here's the code:
beforeEach(module(function ($provide) {
    sideNavToggleMock = jasmine.createSpy('$mdSidenav');
    $provide.factory('$mdSidenav', function() {
        return function(sideNavId) {
            passedSideNavId = sideNavId;
            return {
                toggle: sideNavToggleMock
            };
        };
    });
}));

That allows me to check everything that needs to be checked.  I'll be honest when I say that I'm not 100% certain how that's working, but I know that it works and I'll figure out the "how" part later.  For completeness, here's my full spec:
describe('myController', function() {
    var $controller, scope, sideNavToggleMock, passedSideNavId;
 
    beforeEach(module('app'));

    beforeEach(module(function ($provide) {
        sideNavToggleMock = jasmine.createSpy('$mdSidenav');
        $provide.factory('$mdSidenav', function() {
            return function(sideNavId) {
                passedSideNavId = sideNavId;
                return {
                    toggle: sideNavToggleMock
                };
            };
        });
    }));
 
    beforeEach(inject(function(_$controller_, _$rootScope_){
        scope = _$rootScope_.$new();
        _$controller_('myController', { $scope: scope });
    }));
 
    it('openSidenav should pass \'menu\' to $mdSidenav.toggle', function() { 
        scope.openSidenav();
  
        expect(sideNavToggleMock).toHaveBeenCalled();
        expect(passedSideNavId).toBe('menu');
    });
});

2 comments: