Написание теста для директивы, выполняющей полную перезагрузку страницы в Karma.

Я хотел бы провести модульное тестирование директивы, которая перенаправляет пользователя на URL-адрес социального входа в резервную копию.

поскольку Karma не поддерживает полную перезагрузку страницы, я хотел бы изменить поведение JavaScript-объекта location.href для вывода параметра, который он получает, в HTML-элемент с определенным идентификатором, и у меня возникают трудности с этим.

директива:

__app.directive('socialAuth', function(utils, authService, $location){
    return{
            restrict: 'A',
            scope: false,
            link: function(scope, elem, attrs){
                elem.bind('click', function(){
                            utils.cleanSocialSearch();
                            if(attrs.checkbox == 'personal'){
                              scope.$apply(function(){
                                scope.model.personalShare[attrs.network] = true;  
                                $location.search('personalShare', '1');
                              });
                            }
                            else if(attrs.checkbox == 'group'){
                              scope.$apply(function(){
                                var __index = attrs.checkbox + '_' + attrs.network;
                                scope.model.personalShare[__index] = true;  
                                $location.search('groupShare', '1');
                              });
                            }
                              var callback = encodeURIComponent(window.location.href);
                              var loginUrl = utils.getBaseUrl() + '/social/login/' + attrs.network + '?success_url=' + callback;
                              location.href = loginUrl;  

                        });
            }
    }

});

Тест с попыткой имитировать объект location.href(да, я знаю, что это не функция):

var location = {//an attempt to mock the location href object
  href: function(param){
    $('#content').html(param);
  }
};

'use strict';
    describe('socail-auth', function(){//FB
      var scope, compiled, linkFn, html, elem, elemPersonal, elemGroups, compile, authService;
      html = "<span id='content' data-social-auth data-network='facebook'>";
      beforeEach(function(){
        module('myApp.directives');
        module('myApp.services');

    inject(function($compile, $rootScope){
      scope = $rootScope.$new();
      linkFn = $compile(angular.element(html));
      elem = linkFn(scope);
      scope.$digest();
      elem.scope().$apply()
    });
 })
    it("should redirect user to social login url at the backend", function(){
      // console.log(location.href);
      elem.click();
      console.log($(elem).html());
      expect($(elem).html()).toBeDefined();

    });

});

person Oleg Belousov    schedule 17.11.2013    source источник
comment
вы должны использовать $window.location.href для перезагрузки страницы, чтобы вы могли ее протестировать.   -  person Utopik    schedule 17.11.2013
comment
Тем не менее карма будет жаловаться на полную перезагрузку страницы, мой друг   -  person Oleg Belousov    schedule 17.11.2013


Ответы (2)


Используйте $window.location.href вместо location.href.

Затем смоделируйте $window.location с пустым объектом, он выполнит эту работу.

describe('location', function() {
    var $window;

    beforeEach( module('myApp') );

    // You can copy/past this beforeEach
    beforeEach( module( function($provide) {

        $window = { 
            // now, $window.location.path will update that empty object
            location: {}, 
            // we keep the reference to window.document
            document: window.document
        };

        // We register our new $window instead of the old
        $provide.constant( '$window' , $window );
    }))


    // whatever you want to test
    it('should be redirected', function() {

        // action which reload the page
        $scope.reloadPage();

        // we can test if the new path is correct.
        expect( $window.location.path ).toBe('/the-url-expected');

    })
})
person Utopik    schedule 17.11.2013
comment
Это хорошее и краткое решение, а не такой хак, как то, что я использовал. Но могу ли я изменить значение угловой постоянной? - person Oleg Belousov; 17.11.2013
comment
Да, можете, вот почему тестирование angular.js чертовски круто. вот почему $window и $document существуют, чтобы над ними можно было издеваться. angular-mock.js использует это. - person Utopik; 17.11.2013
comment
Кажется, это работает только в том случае, если ваша директива не выполняет никаких манипуляций с домом. Мой и я получаю TypeError: 'undefined' is not a function (evaluating 'document.createElement('div')') - person JaredMcAteer; 14.02.2014
comment
@JaredMcAteer Вы правы. Я проведу некоторые исследования / тесты для лучшего ответа. Спасибо за ваш отзыв (Если что-то найдете, буду рад услышать ;)) - person Utopik; 14.02.2014
comment
Так что я закончил тем, что обернул $window.location в службу, чтобы я мог перенаправлять на внешние URL-адреса и шпионить за этим объектом в своих тестах. - person JaredMcAteer; 22.02.2014
comment
@JaredMcAteer есть $location. Вы пробовали? - person Utopik; 22.02.2014
comment
$location предназначен для внутренних изменений представления, а не для внешних перенаправлений. из документов: что он не делает? Это не вызывает полной перезагрузки страницы при изменении URL-адреса браузера. Чтобы перезагрузить страницу после изменения URL-адреса, используйте API более низкого уровня, $window.location.href. - person JaredMcAteer; 24.02.2014
comment
@JaredMcAteer, поскольку $window - это просто оболочка для window, мы можем безопасно ссылаться с $window.document на window.document. Я обновил свой ответ. - person Utopik; 25.02.2014
comment
Когда я пробую это решение, я получаю сообщение об ошибке: Ошибка: [$injector:unpr] Неизвестный поставщик: $provideProvider ‹- $provide Как правильно внедрить это в мою тестовую среду? - person Blake; 01.03.2014

Решение состоит в том, чтобы обернуть перенаправление на API в сервисной функции и смоделировать его в тесте, чтобы сохранить URL-адрес перенаправления в сервисной переменной, а затем предоставить его тесту через метод получения вместо перенаправления на него.

Макет можно сделать из тела теста:

module('myApp.services.mock', function($provide){
  var __service = function(){
     var __data;
     return{
        getFn: function(){
          return __data;
        },
        setFn: function: function(data){
          __data = data;
        }
     }
  }
  $provide.value('someService', __service);
});

но я предпочитаю отделить это фиктивное вступление от собственной модели, чтобы улучшить структуру и модульность.

'use strict';
angular.module('myApp.services.mock', ['urlConfig']).
    factory('utils', function(API_URL, $location, $rootScope, $window){
        var __redirectURL = $window.location.href;
        var utilsMock = {
          getBaseUrl: function(){
              return API_URL;
          },
          getLocationHref: function(){
              return __redirectURL;
          },
          setLocationHref: function(redirectURL){
            $rootScope.$apply(function(){
                __redirectURL = redirectURL;
            });

          },
          cleanSocialSearch: function(){
              $location.search('auth_success', null);
              $location.search('auth_error', null);
              $location.search('auth_media', null);
              $location.search('personalShare', null);
              $location.search('groupShare', null);
          }
        }
        return utilsMock;
    });

--

__app.directive('socialAuth', function(utils, $location){
    return{
            restrict: 'A',
            scope: false,
            link: function(scope, elem, attrs){
                elem.bind('click', function(){
                            utils.cleanSocialSearch();
                            if(attrs.checkbox == 'personal'){
                              scope.$apply(function(){
                                scope.model.personalShare[attrs.network] = true;  
                                $location.search('personalShare', '1');
                              });
                            }
                            else if(attrs.checkbox == 'group'){
                              scope.$apply(function(){
                                var __index = attrs.checkbox + '_' + attrs.network;
                                scope.model.personalShare[__index] = true;  
                                $location.search('groupShare', '1');
                              });
                            }
                              var callback = encodeURIComponent(utils.getLocationHref());
                              var loginUrl = utils.getBaseUrl() + '/social/login/' + attrs.network + '?success_url=' + callback;
                              utils.setLocationHref(loginUrl);  

                        });
            }
    }

});

И, наконец, тест:

describe('socail-auth', function(){//FB
  var scope, compiled, linkFn, utils, html, elem, elemPersonal, elemGroups, compile, authService, utilsMock, _window, BASE_URL, __network;
  __network = 'facebook';
  html = "<span data-social-auth data-network='" + __network + "'></span>";

  beforeEach(function(){
    module('urlConfig');
    module('myApp.services.mock');
    module('myApp.directives');
    inject(function($compile, $rootScope, $injector){
      scope = $rootScope.$new();
      utils = $injector.get('utils');
      _window = $injector.get('$window');
      BASE_URL = $injector.get('API_URL');
      linkFn = $compile(angular.element(html));
      elem = linkFn(scope);
      // scope.$digest();
      elem.scope().$apply()  
    });
 })
    it("should redirect user to social login url at the backend", function(){
      elem.click();
      //scope.$digest();
      var loginUrl = BASE_URL + '/social/login/' + __network + '?success_url=' + encodeURIComponent(_window.location.href);


       expect(utils.getLocationHref()).toEqual(loginUrl);


   });
 });
person Oleg Belousov    schedule 17.11.2013