Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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'},
    });
like image 595
Oleg Belousov Avatar asked Mar 18 '23 13:03

Oleg Belousov


1 Answers

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

like image 106
Jesus Rodriguez Avatar answered Apr 08 '23 08:04

Jesus Rodriguez