Search code examples
angularjsunit-testingjasmineangular-filters

how to mock angularJs filter value for unit tests


I am unit testing a function in a controller which is using a translate filter I want to mock the filter with a custom value
I am using $provide for mocking a value but it does nothing
Any advices ?
Thank you very much

My controller :

    function MainCtrl($scope,$filter){
        $scope.onDateChange = onDateChange;

        var date = new Date();
        var date_format_filter = $filter("i18nFilter")("DATE_FORMAT_FILTER");
        // i want this to be dd/mm/yyyy
        var date_format_moment = $filter("i18nFilter")("DATE_FORMAT_MOMENT");
         // i want this to be DD/MM/YYYY
        console.log("DATE FORMAT" , date_format_moment); // print DATE_FORMAT_MOMENT in unit test = test fail
        $scope.startDate = $filter('date')(date , date_format_filter);
        $scope.endDate = $filter('date')(date , date_format_filter);

        function onDateChange(whichDate){

            var startDateFormatted = moment($scope.startDate , date_format_moment).startOf('day').toDate();
            var endDateFormatted =  moment($scope.endDate, date_format_moment).startOf('day').toDate();
                if(startDateFormatted.getTime() > endDateFormatted.getTime()){
                    $scope.startDateError = true;
                    $scope.errorLabel = $filter("i18nFilter")("DATE_START_ERROR");
                }else {
                    $scope.monitoring.startDateError = false;
                }
         }
    }

My unit test

describe('CONTROLLER : MainCtrl', function() {  


    // MAIN VARIABLES ==================================================================
    var $scope,
        $controller,
        $rootScope,
        $filter;


    // LOAD APP MODULE =================================================================
    beforeEach(module('myApp'));


    // SETUP ===========================================================================
    beforeEach(function() {

        module(function($provide) {
            $provide.value('i18nFilter', 'DD/MM/YYYY');
        });

         inject(function ($rootScope , _$controller_ , $injector) {
                $scope          = $rootScope.$new();
                $filter = $injector.get("$filter");
                $controller     = _$controller_;
                $controller = $controller('MainCtrl' , {$scope : $scope  , $filter : $filter});
         });
    });

    // Controller initialization -------------------------------------------------------
    it('- Controller should be defined.', function() {
        expect($controller).toBeDefined();
    });

    it('- Should test startDate change event KO .', inject(function($controller){
        var date_format_moment = 'DD/MM/YYYY';
        $scope.startDate = moment(new Date()).add(1, 'days').format(date_format_moment);
        $scope.endDate = moment(new Date()).format(date_format_moment);
        $scope.onDateChange('startDate');
        expect($scope.startDateError).toEqual(true);
    }));
});

Solution

  • A filter is a function, and filter services have Filter suffix, so it should be i18nFilterFilter.

    beforeEach(module({ i18nFilterFilter: jasmine.createSpy() }));
    
    ...
    i18nFilterFilter.and.returnValues('dd/mm/yyyy', 'DD/MM/YYYY');
    

    A cleaner approach is to reduce the number of moving parts and mock $filter itself,

    beforeEach(module({ $filter: jasmine.createSpy() }));
    
    ...
    var i18nFilterMock = jasmine.createSpy().and.returnValues('dd/mm/yyyy', 'DD/MM/YYYY');
    var dateFilterMock = ...;
    
    $filter.and.returnValues(i18nFilterMock, i18nFilterMock, dateFilterMock, dateFilterMock);
    
    ...
    var ctrl =  $controller('MainCtrl', ...);
    
    expect($filter.calls.count()).toBe(4);
    expect($filter.calls.allArgs()).toEqual([
      ['i18nFilter'], ['i18nFilter'], ['date'], ['date']
    ]);
    
    expect(i18nFilterMock.calls.count()).toBe(2);
    expect($filter.calls.allArgs()).toEqual([
      ['DATE_FORMAT_FILTER'], ['DATE_FORMAT_MOMENT']
    ]);
    

    It should be mentioned that Sinon provides many more features for 'smart' stubs/spies than Jasmine, so both can be used together.