_$modal
_を起動し、返されたプロミスを使用してロジックを実行するコントローラーの単体テストを作成しています。 $ modalを起動する親コントローラーをテストすることはできますが、私は一生の間、成功した約束をモックする方法を理解することはできません。
_$q
_および$scope.$apply()
を使用してプロミスの解決を強制するなど、いくつかの方法を試しました。しかし、私が得た最も近いものは、 this SO post;
私はこれが「古い」_$dialog
_モーダルで何度か尋ねられるのを見ました。 「新しい」_$dialog
_モーダルを使用してそれを行う方法については、あまり見つけることができません。
いくつかのポインタは大歓迎です。
問題を説明するために、 I 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');
};
});
_
ビュー(main.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.
});
});
_
BeforeEachの$ modal.open関数をスパイすると、
spyOn($modal, 'open').andReturn(fakeModal);
or
spyOn($modal, 'open').and.returnValue(fakeModal); //For Jasmine 2.0+
open
モックにレイアウトしたfakeModal
関数を含まない$ modalのモックではなく、$ modal.openが通常返すもののモックを返す必要があります。偽のモーダルには、コールバックを保存するresult
関数を含むthen
オブジェクトが必要です([OK]または[キャンセル]ボタンがクリックされたときに呼び出されます)。また、close
関数(モーダルの[OK]ボタンのクリックをシミュレート)およびdismiss
関数(モーダルの[キャンセル]ボタンのクリックをシミュレート)も必要です。 close
およびdismiss
関数は、呼び出されたときに必要なコールバック関数を呼び出します。
fakeModal
を次のように変更すると、単体テストに合格します。
var fakeModal = {
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
さらに、キャンセルハンドラでテストするプロパティを追加することで、キャンセルダイアログケースをテストできます。この場合は$scope.canceled
:
$scope.modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$scope.canceled = true; //Mark the modal as canceled
$log.info('Modal dismissed at: ' + new Date());
});
キャンセルフラグが設定されると、単体テストは次のようになります。
it("should cancel the dialog when dismiss is called, and $scope.canceled should be true", function () {
expect( scope.canceled ).toBeUndefined();
scope.open(); // Open the modal
scope.modalInstance.dismiss( "cancel" ); //Call dismiss (simulating clicking the cancel button on the modal)
expect( scope.canceled ).toBe( true );
});
ブラントの答えに追加するために、他のシナリオを処理できるように少し改良されたモックを以下に示します。
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
});
modals はプロミスを使用するので、そのようなことには $ 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)