【问题标题】:Mocking a resolve in $dialog在 $dialog 中模拟一个解决方案
【发布时间】:2013-09-27 16:00:15
【问题描述】:

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

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

我正在寻找使它起作用的东西,同时也在寻找它为什么起作用的解释。

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

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

所以我的俱乐部被埋在“解决:对象......”中,我认为这是一个承诺。我认为我需要一种方法来触发它来解决 - 但我不确定那是什么。

【问题讨论】:

    标签: angularjs jasmine karma-runner


    【解决方案1】:

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

    简短的回答是,对话框的 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 属性。

    【讨论】:

    • 为什么不用$injector.invoke()方法在真正的$dialog服务中怎么做?尝试this.club = $injector.invoke(parameters.resolve.club); 而不是this.club = parameters.resolve.club();。并且不要忘记之前注入 $injector 服务
    猜你喜欢
    • 1970-01-01
    • 2022-12-16
    • 1970-01-01
    • 2012-03-31
    • 1970-01-01
    • 2021-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多