angular-ui/bootstrap: Testing a controller that uses a dialog
我有一个控制器,它使用来自 angular-ui/bootstrap 的对话框:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | function ClientFeatureController($dialog, $scope, ClientFeature, Country, FeatureService) { //Get list of client features for selected client (that is set in ClientController) $scope.clientFeatures = ClientFeature.query({clientId: $scope.selected.client.id}, function () { console.log('getting clientfeatures for clientid: ' + $scope.selected.client.id); console.log($scope.clientFeatures); }); //Selected ClientFeature $scope.selectedClientFeature = {}; /** * Edit selected clientFeature. * @param clientFeature */ $scope.editClientFeature = function (clientFeature) { //set selectedClientFeature for data binding $scope.selectedClientFeature = clientFeature; var dialogOpts = { templateUrl: 'partials/clients/dialogs/clientfeature-edit.html', controller: 'EditClientFeatureController', resolve: {selectedClientFeature: function () { return clientFeature; } } }; //open dialog box $dialog.dialog(dialogOpts).open().then(function (result) { if (result) { $scope.selectedClientFeature = result; $scope.selectedClientFeature.$save({clientId: $scope.selectedClientFeature.client.id}, function (data, headers) { console.log('saved.'); }, null); } }); }; }); |
我对测试几乎完全陌生,我想也许我需要测试两件事:
我真正搞砸的测试现在看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | describe('ClientFeatureController', function () { var scope, $dialog, provider; beforeEach(function () { inject(function ($controller, $httpBackend, $rootScope, _$dialog_) { scope = $rootScope; $dialog = _$dialog_; //mock client scope.selected = {}; scope.selected.client = { id: 23805 }; $httpBackend.whenGET('http://localhost:3001/client/' + scope.selected.client.id + '/clientfeatures').respond(mockClientFeatures); $controller('ClientFeatureController', {$scope: scope}); $httpBackend.flush(); }); }); it('should inject dialog service from angular-ui-bootstrap module', function () { expect($dialog).toBeDefined(); console.log($dialog); //{} }); var dialog; var createDialog = function (opts) { dialog = $dialog.dialog(opts); }; describe('when editing a clientfeature', function () { createDialog({}); console.log(dialog); //undefined // var res; // var d; // beforeEach(function () { // var dialogOpts = { // template: 'dummy template' // }; // console.log(dialog); // d = $dialog.dialog(dialogOpts); // d.open(); // }); // // it('should open a dialog when editing a client feature', function () { // expect(d.isOpen()).toBe(true); // }); }); }); |
现在的直接问题是我无法创建/打开对话框。我收到以下错误:
1 2 | Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED TypeError: Cannot call method 'dialog' of undefined |
如果有人已经为类似的用例编写了测试并且可以为我提供一个示例,那就太好了,因为我很迷茫。
谢谢,
肖恩
直到现在,我一直在为同样的问题苦苦挣扎,在浏览了 github 存储库之后,我找到了对话测试并将其用作起点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var $dialog,$scope,$httpBackend; beforeEach(module('ui.bootstrap.dialog')); beforeEach(function(){ inject(function (_$dialog_, _$httpBackend_, $controller){ $dialog = _$dialog_; $httpBackend = _$httpBackend_; $httpBackend.expectGET('/appServer/list') .respond([{ id:1, name:'test1' }, { id:2, name:'test2' }, { id:3, name:'test3' }]); //setup controller scope scope = {}; ServerCtrl = $controller('ServerCtrl', { $scope: scope, $dialog:$dialog }); }); }); |
我也更喜欢适当的模拟。当它不可用时,我修补服务
对此进行测试:
1 2 3 4 5 6 7 | $dialog.messageBox(title, msg, btns) .open() .then(function (result) { if (result == 'ok') { // block executed if user click OK } }); |
你可以像这样给 $dialog 打补丁:
1 2 3 4 5 6 7 8 9 10 11 | $dialog.messageBox = function (title, msg, btns) { return { open: function () { return { then: function (callback) { callback('ok'); // 'ok' will be set to param result } } } } }; |
我发现编写我自己的对话框模拟是最清晰的。这是一个模拟对话框以模拟选择"是"的示例。
被测代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | .controller('AdminListingCtrl', function AdminListingController($scope, $dialog, houseRepository) { $scope.houses = houseRepository.query(); $scope.remove = function (house) { var dlg = $dialog.messageBox('Delete house', 'Are you sure?', [ {label: 'Yep', result: 'yes'}, {label: 'Nope', result: 'no'} ]); dlg.open().then(function (result) { if (result == 'yes') { houseRepository.remove(house.id); $scope.houses = houseRepository.query(); } }); }; } |
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | describe('when deleting a house', function () { var fakeDialog = { open: function() { return { then: function(callback) { callback("yes"); } }; } }; beforeEach(inject(function($dialog) { spyOn($dialog, 'messageBox').andReturn(fakeDialog); })); it('should call the remove method on the houseRepository', function () { scope.remove({id: 99}); expect(houseRepository.remove).toHaveBeenCalledWith(99); }); // etc }); |
我个人尝试模拟所有服务。如果 ui-bootstrap 项目不提供 $dialog 模拟,您应该在那里打开一个错误票并要求他们提供一个。但是创建一个同样容易。
模拟服务应该有虚假的方法,除了返回Promise之外什么都不做。它还应该为您提供一种刷新所有异步方法的方法,以便更轻松地进行同步测试。