Как обмануть $window.location.replace в AngularJS unit test?
У меня есть следующая услуга:
angular.module("services")
.factory("whatever", function($window) {
return {
redirect: function() {
$window.location.replace("http://www.whatever.com");
}
};
});
Как обмануть объект $window
в unit test, чтобы предотвратить перезагрузку страницы при выполнении тестов?
Я попытался использовать
spyOn($window.location, 'replace').andReturn(true);
но он не работает (по-прежнему получена ошибка "Some of your tests did a full page reload!"
) и
$provide.value('$window', {location: {replace: jasmine.createSpy()}})
но я получал ошибку (Error: [ng:areq] Argument 'fn' is not a function, got Object
) с трассировкой стека, указывающей только на angular собственный источник, поэтому он не очень помог...
Ответы
Ответ 1
В Chrome (не тестировалось в других браузерах) location.replace читается только так, что spyOn не смог его заменить.
$provide.value
должен работать. Что-то должно быть не так где-то в вашем коде.
Вот работающий unit test
describe('whatever', function() {
var $window, whatever;
beforeEach(module('services'));
beforeEach(function() {
$window = {location: { replace: jasmine.createSpy()} };
module(function($provide) {
$provide.value('$window', $window);
});
inject(function($injector) {
whatever = $injector.get('whatever');
});
});
it('replace redirects to http://www.whatever.com', function() {
whatever.redirect();
expect($window.location.replace).toHaveBeenCalledWith('http://www.whatever.com');
});
});
Ответ 2
Я собираюсь с более легким, но, возможно, менее элегантным решением. Я пишу обертку для $window.location, которую я могу затем высмеять. Относясь к вашему коду, я бы издевался над функцией any.redirect, а не издеваясь над $window (я предполагаю, что ваша реальная функция сложнее).
Итак, я получаю:
angular.module("services")
.factory("whatever", function($window) {
return {
do_stuff_that_redirects: function() {
lots of code;
this.redirect("http://www.whatever.com");
maybe_more_code_maybe_not;
},
redirect: function(url) {
$window.location.replace(url);
}
};
});
Затем я могу непосредственно высмеять метод перенаправления и просто верю, что поскольку это только одна строка кода, это не может пойти не так.
spyOn(whatever, 'redirect').andCallFake(function(){});
expect(whatever.redirect).toHaveBeenCalledWith('http:/my.expected/url');
Этого достаточно для моих целей и позволяет проверять URL-адрес.
Ответ 3
Я предлагаю другой подход, который может сработать для вас. Я столкнулся с такой же проблемой, пока модуль тестировал "действие" контроллера, которое в конечном итоге перенаправляет пользователя (полностраничная загрузка, но на другую страницу на более крупном веб-сайте/приложении). Чтобы дать какой-то контекст, контроллер запускает запрос AJAX, и если ответ в порядке, он перенаправляет пользователя на другую страницу через $window.location.replace():
$http.post('save', data)
.success(function(responseData, status, headers, config) {
if(responseData.redirect) {
$window.location.replace(responseData.redirect);
}
})
.error(function(responseData, status, headers, config) {
console.error("ERROR while trying to create the Event!!");
});
Тест для этой функции контроллера вызвал то же самое: "Некоторые из ваших тестов сделали полную перезагрузку страницы!" ошибка. Поэтому я добавил следующее к функции beforeEach() для спецификации контроллера, чтобы изрубить службу $window:
mockWindow = { location: { replace: function(url) { console.log('redirecting to: ' + url); } } };
eventCtrl = $controller('EventCtrl', { $scope: scope, $window: mockWindow });
Конечно, это решение мешает мне (чисто) проверять, что функция replace была вызвана с ожидаемым аргументом, но мне сейчас это не очень важно... Надеюсь, что это поможет.
Ответ 4
Я думаю, что вы хотите использовать службу $location, а не вызов $window.location
. Существует также целая страница, объясняющая эту функцию здесь: http://docs.angularjs.org/guide/dev_guide.services.$location.
Используя это, должно быть довольно просто использовать в тестах пропущенную версию службы определения местоположения $.
Ответ 5
$location.path('/someNewPath');
$location.replace();
// или вы можете связать их как: $location.path('/someNewPath'). replace();