2
votes

I've a controller that uses a Dialog from angular-ui/bootstrap:

   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);
                }
            });
        };
    });

I'm almost completely new to testing, and figured that maybe I need to test two things:

  1. That a dialog opens when $scope.editClientFeature() is called
  2. That $save is called successfully after a dialog is closed and returns a 'result'

My really messed up test now looks like this:

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: '<div>dummy template</div>'
//            };
//            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);
//        });
    });

});

The immediate problem now is that I'm unable to create/open a dialog. I get the following error:

Chrome 25.0 (Mac) ClientFeatureController when editing a clientfeature encountered a declaration exception FAILED
    TypeError: Cannot call method 'dialog' of undefined

It would be great if someone has already written a test for a similar use case and can provide me with an example as I'm pretty lost.

Thanks, Shaun

4

4 Answers

6
votes

I was struggling with the same problem until right now, after trolling the the github repo i found the dialog tests and used that as a starting point :

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
      });
    });
  });
3
votes

I also prefer a proper mock. When it is not available, i patch the service

To test this:

$dialog.messageBox(title, msg, btns)
   .open()
   .then(function (result) {
       if (result == 'ok') {
            // block executed if user click OK
       }
});

You can patch $dialog like this:

$dialog.messageBox = function (title, msg, btns) {
    return {
        open: function () {
            return {
                 then: function (callback) {
                      callback('ok'); // 'ok' will be set to param result
                 }
             }
        }
    }
 };
2
votes

Personally I try to mock all services out. If the ui-bootstrap project does not provide a $dialog mock, you should open a bug ticket there and ask them for one. However creating one is as easy.

The mock service should have fake methods that do nothing but return promises. It should also give you a method to flush all asynchronous methods to make it easier to do synchronous testing.

2
votes

I find it clearest to write my own mock of the dialog. Here's an example of mocking out a dialog to simulate "yes" being chosen.

Code under test

.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();
            }
        });
    };
}

Tests

    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
    });