looneytunes looneytunes - 2 months ago 10
AngularJS Question

how to unit test custom $filter by mocking in ES6 class for angular controller

I have a ES Class written for a angular controller and i am trying to write jasmine tests using angular-mock.

In the constructor I initialize $filter to this.i18n = $filter('i18n), which is basically a localization filter that takes a key, value and returns teh localized value for the key.

My problem is that since teh constructor of the class has the $filter and is later on used in the class methods. My unit tests fail. How could i test using $filter such that my tests dont fail. I am looking to mock the custom filter

Here is the exception that i get

TypeError: this.i18n is not a function
at UsersCtrl.$onInit (test-context.js:35081:41)
at Object.<anonymous> (test-context.js:35031:31)


Here is the ES6 Class

class UsersCtrl {
constructor($filter) {
this.i18n = $filter('i18n');
this.users = [];
}
//Life cycle Hooks: Initialization
$onInit() {
this.users = [
{name: this.i18n('USERS')}
];
}
}
UsersCtrl.$inject = ['$filter'];

angular.module('app', []).controller('UsersCtrl',UsersCtrl);

export default UsersCtrl;


Here is the Unit test that i have

Import UsersCtrl from './users-controller.js';

describe("given a new User Page", () => {
var UserController;
beforeEach(() => {
angular.mock.module('app');
});

describe("when initialising has completed", () => {
beforeEach(() => {
inject(($rootScope, $controller, $filter) => {
const scope = $rootScope.$new();
const filter = $filter
UserController = $controller("UsersCtrl", { $scope: scope, $filter: filter});
});
});
it("then users array for tab content should be empty initially",() => {
const expectedActive = [];
expect(UserController.users).toEqual(expectedActive);
});
});
});


The issue is it runs the test but fails as its unable to initialize with this.i18n = $filter('i18n).How should i initialize the test to pass $filter into the tests? Could i mock the filter as i dont really want to test the filter here

JcT JcT
Answer

You could either:

  • Mock the entire $filter service and inject that into the controller when you instantiate it in your test; or
  • Mock the individual filter. You can then use $provide.value(key, value) when you load your module, where the key is the filter name plus a suffix: 'Filter' (this is something angular does internally when registering filters).

An example of both:

/* Angular App */
(function() {
  "use strict";

  class UsersCtrl {
    constructor($filter) {
      this.i18n = $filter('i18n');
      this.users = [];
    }

    $onInit() {
      this.users = [{
        name: this.i18n('USERS')
      }];
    }
  }

  UsersCtrl.$inject = ['$filter'];

  angular
    .module('app', [])
    .controller('UsersCtrl', UsersCtrl);

})();

/* Unit Test */
(function() {
  "use strict";

  describe('given a new User Page', () => {
    let UsersController;

    describe('MOCK entire $filter service: when initialising has completed', () => {
      beforeEach(() => {
        module('app');

        inject(($rootScope, $controller) => {
          UsersController = $controller("UsersCtrl", {
            $scope: $rootScope.$new(),
            $filter: () => {
              // $filter will be a function that returns a noop function (or whatever we want)
              return angular.noop;
            }
          });
        });
      });

      it('then users array for tab content should be empty initially', () => {
        const expectedActive = [];
        expect(UsersController.users).toEqual(expectedActive);
      });
    });

    describe('MOCK individual \'i18n\' filter: when initialising has completed', () => {
      beforeEach(() => {
        module('app', ($provide) => {
          // provide a mock (noop function) for our filter.
          // Note naming convention, our filter name + 'Filter' suffix.
          $provide.value('i18nFilter', angular.noop);
        });

        inject(($rootScope, $controller, $filter) => {
          UsersController = $controller("UsersCtrl", {
            $scope: $rootScope.$new(),
            $filter: $filter
          });
        });
      });

      it('then users array for tab content should be empty initially', () => {
        const expectedActive = [];
        expect(UsersController.users).toEqual(expectedActive);
      });
    });

  });

})();
<link rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.css" />
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine.js"></script>
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html.js"></script>
<script src="//cdn.jsdelivr.net/jasmine/2.0.0/boot.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-mocks.js"></script>

Comments