Насмешка $modal в модульных тестах AngularJS
Я пишу модульный тест для контроллера, который запускает $modal
и использует обещание, возвращенное для выполнения некоторой логики. Я могу проверить Родительский контроллер, который запускает $ modal, но я не могу за всю свою жизнь понять, как издеваться над успешным обещанием.
Я пробовал несколько способов, в том числе с помощью $q
и $scope.$apply()
чтобы заставить разрешение обещания. Однако самое близкое, что я получил, - это собрать что-то похожее на последний ответ в этой так пост;
Я видел, как это спрашивали несколько раз со "старым" $dialog
модала.
Я не могу найти, как это сделать с "новыми" $dialog
модала.
некоторые указатели будут оценены tres.
чтобы проиллюстрировать проблему, я использую пример, приведенный в UI Bootstrap docs С небольшими правками.
контроллеры (основные и модальные)
'use strict';
angular.module('angularUiModalApp')
.controller('MainCtrl', function($scope, $modal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.open = function() {
$scope.modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
resolve: {
items: function() {
return $scope.items;
}
}
});
$scope.modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
$log.info('Modal dismissed at: ' + new Date());
});
};
})
.controller('ModalInstanceCtrl', function($scope, $modalInstance, items) {
$scope.items = items;
$scope.selected = {
item: $scope.items[0]
};
$scope.ok = function() {
$modalInstance.close($scope.selected.item);
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
});
посмотреть (главный.html)
<div ng-controller="MainCtrl">
<script type="text/ng-template" id="myModalContent.html">
<div class="modal-header">
<h3>I is a modal!</h3>
</div>
<div class="modal-body">
<ul>
<li ng-repeat="item in items">
<a ng-click="selected.item = item">{{ item }}</a>
</li>
</ul>
Selected: <b>{{ selected.item }}</b>
</div>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<button class="btn btn-default" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
тест
'use strict';
describe('Controller: MainCtrl', function() {
// load the controller's module
beforeEach(module('angularUiModalApp'));
var MainCtrl,
scope;
var fakeModal = {
open: function() {
return {
result: {
then: function(callback) {
callback("item1");
}
}
};
}
};
beforeEach(inject(function($modal) {
spyOn($modal, 'open').andReturn(fakeModal);
}));
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope, _$modal_) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
$modal: _$modal_
});
}));
it('should show success when modal login returns success response', function() {
expect(scope.items).toEqual(['item1', 'item2', 'item3']);
// Mock out the modal closing, resolving with a selected item, say 1
scope.open(); // Open the modal
scope.modalInstance.close('item1');
expect(scope.selected).toEqual('item1');
// No dice (scope.selected) is not defined according to Jasmine.
});
});
4 ответа:
когда вы шпионите за $modal.откройте функцию в beforeEach,
spyOn($modal, 'open').andReturn(fakeModal); or spyOn($modal, 'open').and.returnValue(fakeModal); //For Jasmine 2.0+
вам нужно вернуть макет того, что $modal.open обычно возвращает, а не макет $modal, который не включает в себя
чтобы добавить к ответу Бранта, вот немного улучшенный макет, который позволит вам обрабатывать некоторые другие сценарии.
var fakeModal = { result: { then: function (confirmCallback, cancelCallback) { this.confirmCallBack = confirmCallback; this.cancelCallback = cancelCallback; return this; }, catch: function (cancelCallback) { this.cancelCallback = cancelCallback; return this; }, finally: function (finallyCallback) { this.finallyCallback = finallyCallback; return this; } }, close: function (item) { this.result.confirmCallBack(item); }, dismiss: function (item) { this.result.cancelCallback(item); }, finally: function () { this.result.finallyCallback(); } };
это позволит макет обрабатывать ситуации, где...
вы используете модальный с
.then()
,.catch()
и.finally()
стиль обработчика вместо передачи 2 функций (successCallback, errorCallback
) к.then()
, например:modalInstance .result .then(function () { // close hander }) .catch(function () { // dismiss handler }) .finally(function () { // finally handler });
С модальности используйте обещания, которые вы обязательно должны использовать $q для таких вещей.
код выглядит так:
function FakeModal(){ this.resultDeferred = $q.defer(); this.result = this.resultDeferred.promise; } FakeModal.prototype.open = function(options){ return this; }; FakeModal.prototype.close = function (item) { this.resultDeferred.resolve(item); $rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply(). }; FakeModal.prototype.dismiss = function (item) { this.resultDeferred.reject(item); $rootScope.$apply(); // Propagate promise resolution to 'then' functions using $apply(). }; // .... // Initialize the controller and a mock scope beforeEach(inject(function ($controller, $rootScope) { scope = $rootScope.$new(); fakeModal = new FakeModal(); MainCtrl = $controller('MainCtrl', { $scope: scope, $modal: fakeModal }); })); // .... it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () { expect( scope.canceled ).toBeUndefined(); fakeModal.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal) expect( scope.canceled ).toBe( true ); });
ответ Бранта был явно удивительным, но это изменение сделало его еще лучше для меня:
fakeModal = opened: then: (openedCallback) -> openedCallback() result: finally: (callback) -> finallyCallback = callback
затем в испытательной зоне:
finallyCallback() expect (thing finally callback does) .toEqual (what you would expect)