Search code examples
angularjsunit-testingkarma-jasmineangular-mock

How do I mock $window injected manually in provider private function?


I have the following provider:

angular.module('MyApp').provider('MyDevice', function () {

    var ngInjector = angular.injector(['ng']),
        $window = ngInjector.get('$window');

    function isMobileDevice () {
        return (/iPhone|iPod|iPad|Silk|Android|BlackBerry|Opera Mini|IEMobile/)
            .test($window.navigator.userAgent || $window.navigator.vendor || $window.opera);
    }

    this.$get = function () {
        return {
            isDesktop: function () {
                return !isMobileDevice();
            },
            isMobile: function () {
                return isMobileDevice();  
            }
        };
    };

});

And the following test spec:

describe('MyDeviceProvider', function () {

    var myDevice;

    beforeEach(function () {
        inject(['MyDevice', function (_myDevice_) {
            myDevice = _myDevice_;
        }]);
    });

    it('Test #1', function () {
        // Mock '$window.navigator.userAgent' to "desktop"
        expect(myDevice.isDesktop()).toEqual(true);
        expect(myDevice.isMobile()).toEqual(false);
    });

    it('Test #2', function () {
        // Mock '$window.navigator.userAgent' to "mobile"
        expect(myDevice.isDesktop()).toEqual(false);
        expect(myDevice.isMobile()).toEqual(true);
    });

});

My question is, how do I mock $window in both Test #1 and Test #2 so they are successful? I have tried with $provide.value and spyOn for countless objects, but I can't seem to mock the value of $window.navigator.userAgent to run my tests.

How do I solve this?

P.S: The code above acts only as a demonstration of my issue and I cannot change the provider into a service because of special requirements of the application.


Solution

  • Very crudely, you could do the following:

    describe('MyDeviceProvider', function () {
    
        var myDevice,
            $window,
            navigator;
    
        beforeEach(function () {
            inject(['MyDevice', '$window', function (_myDevice_, _$window_) {
                myDevice = _myDevice_;
                $window = _$window_;
            }]);
    
            // Save the original navigator object
            navigator = $window.navigator;
        });
    
        afterEach(function () {
            $window.navigator = navigator;
        });
    
        it('Test #1', function () {
            // Mock the entire navigator object to "desktop"
            $window.navigator = {
                userAgent: "desktop" // Use a real "desktop" user agent
            };
    
            // Mock '$window.navigator.userAgent' to "desktop"
            expect(myDevice.isDesktop()).toEqual(true);
            expect(myDevice.isMobile()).toEqual(false);
        });
    
        it('Test #2', function () {
            // Mock the entire navigator object to "desktop"
            $window.navigator = {
                userAgent: "mobile" // Use a real "mobile" user agent
            };
            // Mock '$window.navigator.userAgent' to "mobile"
            expect(myDevice.isDesktop()).toEqual(false);
            expect(myDevice.isMobile()).toEqual(true);
        });
    
    });
    

    You should test different mocks mimicking different browsers too.