Oleg Tikhonov Oleg Tikhonov -4 years ago 235
AngularJS Question

Mocking $routeParams in a test in order to change its attributes dynamically

I have a controller test that depends on the Angular $routeParams service:

var $routeParams, MainCtrl, scope;
beforeEach(inject(function ($controller, $rootScope, $injector, $templateCache) {
scope = $rootScope.$new();
$routeParams = $injector.get('$routeParamsMock');
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: $routeParams,
});
}));

it('should load a pg from $routeParams', function(){
scope.userData = {};
$routeParams._setPg('PG_FIRST');
scope.$digest();
timeout.flush();
expect(scope.userData.pg).toBe(0);

$routeParams._setPg('PG_SECOND');
scope.$digest();
timeout.flush();

expect(scope.userData.pg).toBe(1);
});


$routeParamsMock:

!(function(window, angular){
'use strict';
angular.module('vitaApp')
.service('$routeParamsMock', function() {
var _pg = null;
return{
pg: _pg,
_setPg: function(pg){
_pg = pg;
}
}
});
})(window, window.angular);


When debugging the test, I was surprised to find out that $routeParamsMock.pg was returning null every single time, even though I called _setPg with a different value.

Is it because null is considered a primitive (with a type of object...), and thus passed by value?, or perhaps because Angular is copying the object that is passed to the $controller service?.

The solution I am looking for is preferably one that won't require to instanciate different controllers per different test scenerios.
eg:

MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: {'pg': 'PG_FIRST'},
});

MainCtrl = $controller('MainCtrl', {
$scope: scope,
$routeParams: {'pg': 'PG_SECOND'},
});

Answer Source

The thing is, what you don't want to do, is probably the best solution you have. A mock makes sense when what you want to mock is kinda complex. Complex dependency with methods, lot of states, etc. For a simple object like $routeParams it makes all the sense of the world to just pass a dummy object to it. Yes it would require to instantiate different controllers per test, but so what?

Structure your tests in a way that makes sense, makes it readable and easy to follow.

I suggest you something like:

describe('Controller: Foo', function() {
  var $controller, $scope;

  beforeEach(function() {
    module('app');

    inject(function($rootScope, _$controller_) {
      $scope = $rootScope.$new();routeParams = {};

      $controller = _$controller_;
    });
  });

  describe('With PG_FIRST', function() {
    beforeEach(function() {
      $controller('Foo', { $scope: $scope, $routeParams: {'PG': 'PG_FIRST'}}); 
    });

    it('Should ....', function() {
      expect($scope.something).toBe('PG_FIRST');
    });
  });

  describe('With PG_SECOND', function() {
    beforeEach(function() {
      $controller('Foo', { $scope: $scope, $routeParams: {'PG': 'PG_SECOND'}}); 
    });

    it('Should ....', function() {
      expect($scope.something).toBe('PG_SECOND');
    });
  });
});

With a good test organization, I can say that I like this test easy to follow.

http://plnkr.co/edit/5Q3ykv9ZB7PuGFMfWVY5?p=preview

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download