3

我一直在为我打算编写的应用程序创建一个 angularjs 框架。目前我正在开发一个示例应用程序,我正在编写一个教程,以便将我所做的一切都放在一个地方。

我目前正在尝试使用 karma 和 jasmine 为我正在呈现的模态对话框创建单元测试。这个模态对话框是使用 angular-bootstrap 的 $dialog 服务创建的。我认为这个对话框使用了一个承诺将数据传递到对话框控制器,我想解决这个承诺,这样我就可以在我的单元测试中检查传入的数据是否符合预期。我在弄清楚如何解决这个问题时遇到了一点困难,我看到了使用 scope.$apply 或 scope.$digest 的示例,它们似乎都不起作用,坦率地说,我不太明白它在做什么。我担心在单元测试中我已将此承诺分配给一个变量,并且一旦分配给一个变量,它可能无法解决。我看到提到这个“解决”

我既在寻找使它起作用的东西,又在寻找它为什么起作用的解释。

我要测试的控制器如下所示:

.controller( 'ClubCtrl', function ClubController( $scope, ClubRes, $dialog ) {
  $scope.clubs = ClubRes.query();

  /* this is called from a button, which passes one of the clubs from $scope.clubs */
  $scope.editClub = function(club) {
    $scope.myDialog = $dialog.dialog({dialogFade: false, resolve: {club: function(){return angular.copy(club);}}});
    $scope.myDialog.open('club/club_edit.tpl.html', 'ClubEditCtrl').then(function(result){
      if (result === 'cancel'){}
      else {
        $scope.clubs = ClubRes.query();
      }
    });  
  };
})

我现在尝试进行的单元测试旨在模拟整个对话框,并检查是否使用正确的输入参数调用了对话框:

describe( 'Base club controller', function() {
  var scope, httpBackend;

  //mock Application to allow us to inject our own dependencies
  beforeEach(angular.mock.module('league'));

  //mock the controller for the same reason and include $rootScope and $controller
  beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_ ){
    //create an empty scope
    scope = $rootScope.$new();

    // setup a mock for the resource - instead of calling the server always return a pre-canned response
    httpBackend = _$httpBackend_;
    httpBackend.when('GET', '../clubs.json').respond([
      {"contact_officer":"Officer 1","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":1,"name":"Club 1","updated_at":"2012-03-03T00:00:00Z"},
      {"contact_officer":"Officer 2","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":2,"name":"Club 2","updated_at":"2012-03-03T00:00:00Z"}]);

    // setup a mock for the dialog - when called it returns the value that was input when it was instantiated
    scope.fakeDialog = {
      parameters: null,
      response: null,
      template: null,
      controller: null,
      dialog: function(parameters) {
        this.parameters = parameters;
        return this;
      },
      open: function(template, controller) {
        this.template = template;
        this.controller = controller;
        return this;
      },
      then: function(callBack){
        callBack(this.response);
      }
    };

    //declare the controller and inject our empty scope
    $controller('ClubCtrl', {$scope: scope, $dialog: scope.fakeDialog});
  }));

  it('Calls edit on first row', function() {
    // check nothing set beforehand
    expect(scope.fakeDialog.parameters).toBe(null);
    expect(scope.fakeDialog.template).toBe(null);
    expect(scope.fakeDialog.controller).toBe(null);

    // call edit
    scope.editClub(scope.clubs[0]);
    scope.$digest();
    httpBackend.flush();

    // expect stuff to have happened
    expect(scope.fakeDialog.parameters.club.name).toBe('Club 1');
    expect(scope.fakeDialog.template).toBe('club/club_edit.tpl.html');
    expect(scope.fakeDialog.controller).toBe('ClubEditCtrl');
  });

});

我在 console.log(scope.fakeDialog.parameters) 中实际得到的是:

Object{dialogFade: false, resolve: Object{club: function (){ ... }}}

所以我的club被埋在了“resolve: Object……”里面,我认为这是一个承诺。我认为我需要的是一种触发解决问题的方法-但我不确定那是什么。

4

1 回答 1

4

好的,目前还没有回复,我今晚有时间慢慢整理。

简短的回答是,对话框的 resolve 参数不一定是一个承诺(尽管我认为有时如果你希望它是)。由于我没有通过承诺,我可以直接评估这些函数以计算出它们的结果,尽管我认为我之前已经尝试过但没有成功。

我还花了一些时间研究 spyOn,我可以将它用于我的模拟所做的一些事情,所以我同时也在整理它。

我的工作代码如下。首先,正在测试的控制器:

.controller( 'ClubCtrl', function ClubController( $scope, ClubRes, $dialog ) {
  $scope.clubs = ClubRes.query();

  /* this is called from a button, which passes one of the clubs from $scope.clubs */
  $scope.editClub = function(club) {
    $scope.myDialog = $dialog.dialog({dialogFade: false, resolve: {club: function(){return angular.copy(club);}}});
    $scope.myDialog.open('club/club_edit.tpl.html', 'ClubEditCtrl').then(function(result){
      if (result === 'cancel'){}
      else {
        $scope.clubs = ClubRes.query();
      }
    });  
  };
})

然后,测试的测试代码:

describe( 'Base club controller', function() {
  var scope, httpBackend;

  //mock Application to allow us to inject our own dependencies
  beforeEach(angular.mock.module('league'));

  //mock the controller for the same reason and include $rootScope and $controller
  beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_ ){
    //create an empty scope
    scope = $rootScope.$new();

    // setup a mock for the resource - instead of calling the server always return a pre-canned response
    httpBackend = _$httpBackend_;
    httpBackend.when('GET', '../clubs.json').respond([
       {"contact_officer":"Officer 1","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":1,"name":"Club 1","updated_at":"2012-03-03T00:00:00Z"},
      {"contact_officer":"Officer 2","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":2,"name":"Club 2","updated_at":"2012-03-03T00:00:00Z"}]);

    // setup a mock for the dialog - when called it returns the value that was input when it was instantiated
    scope.fakeDialog = {
      response: null,
      club: null,
      dialog: function(parameters) {
        this.club = parameters.resolve.club();
        return this;
      },
      open: function(template, controller) {
        return this;
      },
      then: function(callBack){
        callBack(this.response);
      }
    };

    //declare the controller and inject our empty scope
    $controller('ClubCtrl', {$scope: scope, $dialog: scope.fakeDialog});
  }));

  it('Calls edit on first row', function() {
    // we expect the fakeDialog dialog and open methods to be called, so we spy on them to get the parameters
    spyOn(scope.fakeDialog, "dialog").andCallThrough();
    spyOn(scope.fakeDialog, "open").andCallThrough();

    // call edit
    scope.editClub(scope.clubs[0]);
    scope.$digest();
    httpBackend.flush();

    // check parameters passed in
    expect(scope.fakeDialog.dialog).toHaveBeenCalledWith({dialogFade: false, resolve: {club: jasmine.any(Function)}});
    expect(scope.fakeDialog.club.contact_officer).toEqual('Contact Officer 1');
    expect(scope.fakeDialog.open).toHaveBeenCalledWith('club/club_edit.tpl.html', 'ClubEditCtrl');
  });
});

这似乎调用了该​​函数并将响应提供给 fakeDialog 对象上的 club 属性。

于 2013-09-27T08:00:15.407 回答