0101adm 0101adm - 4 months ago 54x
AngularJS Question

angular + jasmine + mock $stateParams in a directive

What is the best approach to mock $stateParams in a directive? $stateParam members will be changed according to the test.

I can easily mock $stateParams in a controller using $controller('ctrl', $stateParams) but dont know how to modify $stateParams that gets injected into the directive.

I've gone the route of decorating $stateParams with the below but can only declare that when i create the module. as i mentioned, $stateParam members will change many times through the different tests.

beforeEach(angular.mock.module(function ($provide) {

$provide.provider('$stateParams', function () {
return {
myStateParam: true,
myOtherStateParam: 'some text'



Using directive controller to handle route-related logic ($state/$route) is always a good idea, that's the job for controller and not for linking functions.

In the case of a directive that could be tested completely just by testing its controller and mocking its local dependencies (credits go to JB Nizet's answer) it is the end of the story:

$controller('...', { $scope: scope, $stateParams: { ... });

It isn't the case for regular directive spec with $compile. $compile uses $controller internally to instantiate the controller but doesn't accept locals to mock its dependencies.

There are several ways to handle this.


If $stateParams does not have to be injected into spec directly and thus is uninstantiated, its value can be defined after the module was bootstrapped. When service instance value should be mocked only once per spec, look no further:

var stateParams;

beforeEach(module(('...', function ($provide) {
    $provide.factory('$stateParams', function () {
        return stateParams;

it('...', inject(function ($compile) {
  stateParams = { ... };


$provide service provider can be exposed as service instance to (re)define services after the module was bootstrapped:

beforeEach(module('...', function ($provide) {
  $provide.value('$provide', $provide);

it('...', inject(function ($provide, $compile) {
  $provide.constant('$stateParams', { ... });

$provide.constant is preferable in this case because it will replace cached $stateParams service instance if it was injected before, while $provide.value won't.


angular.copy (which actually was used by UI Router 0.x to keep $stateParams object up to date) can be used to preserve existing object references but replace its contents:

it('...', inject(function ($stateParams, $compile) {
  angular.copy({ ... }, $stateParams);


$state service has little-known yet publicly exposed to API property params, it can be used as a replacement for $stateParams service all over the code to improve its testablity.

$stateParams and $state.params may not refer to the same object, so the choice has to be made in favour of one of them.

At the cost of the risk of test fragility due to of unsolicited $state injection, it can be as neat as

it('...', inject(function ($state, $compile) {
  $state.params = { ... };