如何测试在具有promise的控制器中保存资源

时间:2014-10-22 13:25:09

标签: angularjs testing

我有一个保存资源的控制器。我无法告诉他如何访问"在promise解析后执行的代码部分。我需要对我的测试或控制器进行哪些更改才能使其正常工作?这是代码。

控制器:

'use strict';

/**
 * @ngdoc function
 * @name lunchHubApp.controller:AnnouncementsCtrl
 * @description
 * # AnnouncementsCtrl
 * Controller of the lunchHubApp
 */
angular.module('lunchHubApp')
  .controller('AnnouncementsCtrl', ['$scope', 'Announcement', function ($scope, Announcement) {
    $scope.announcements = [];

    $scope.save = function() {

      // This next line is the part I'm finding hard to test.
      new Announcement($scope.announcement).create().then(function(announcement) {
        $scope.foo = 'bar'
      });
    };
  }]);

测试:

'use strict';

describe('AnnouncementsCtrl', function() {
  beforeEach(function() {
    module('lunchHubApp', 'ng-token-auth')
  });

  it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
    var scope = $rootScope.$new(),
        ctrl = $controller('AnnouncementsCtrl', { $scope: scope });

    expect(scope.announcements).toEqual([]);
  }));

  describe('save', function() {
    it('works', inject(function($controller, $rootScope, _$httpBackend_) {
      var $httpBackend = _$httpBackend_;
      var scope = $rootScope.$new(),
          ctrl = $controller('AnnouncementsCtrl', { $scope: scope });

      expect(scope.announcements.length).toBe(0);

      var announcement = {
        restaurantName: 'Bangkok Taste',
        userId: 1
      };
      scope.announcement = announcement;

      $httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
      scope.save();
      scope.$digest();

      expect(scope.foo).toEqual('bar');
    }));
  });
});

更新:这就是我最终修改控制器测试的方式。以下通过并已从原始版本重构。

'use strict';

describe('AnnouncementsCtrl', function() {
  var $httpBackend,
      announcement,
      scope,
      ctrl;

  beforeEach(function() {
    module('lunchHubApp');

    inject(function($injector) {
      $httpBackend       = $injector.get('$httpBackend');
      scope              = $injector.get('$rootScope').$new();
      ctrl               = $injector.get('$controller')('AnnouncementsCtrl', { $scope: scope });
      announcement       = { restaurantName: 'Bangkok Taste' };
      scope.announcement = { restaurantName: 'Jason\'s Pizza' };

      $httpBackend.expect('GET', '/api/announcements').respond([announcement]);
    });
  });

  it('sets scope.announcements to an empty array', function() {
    expect(scope.announcements).toEqual([]);
  });

  it('grabs a list of announcements', function() {
    expect(scope.announcements.length).toBe(0);
    $httpBackend.flush();
    expect(scope.announcements.length).toBe(1);
  });

  describe('save', function() {
    beforeEach(function() {
      $httpBackend.expect('POST', '/api/announcements').respond(200, { restaurantName: 'Foo' });
      scope.save();
      $httpBackend.flush();
    });

    it('adds an announcement', function() {
      expect(scope.announcements.length).toBe(2);
    });

    it('clears the restaurant name', function() {
      expect(scope.announcement.restaurantName).toEqual('');
    });
  });
});

2 个答案:

答案 0 :(得分:1)

我认为你所做的事情很好。由于 Angular资源是以 restful方式使用$http服务的工厂,因此您应该使用$httpBackendexpect就像你一样。

但是,您要错过的一件事是您需要确保您的承诺得到解决。但在某些情况下,写异步测试可能会很棘手。为此,您必须使用 $ httpBackend flush() 方法强制您的测试同步。

在同花顺之后,您可以正常使用expect。此外,您可能需要在expectPOST声明之前移动$rootScope.$new()

你可以接受这样的改变,我不认为$digest()是必要的:

$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();

$httpBackend.flush();
expect(scope.foo).toEqual('bar');

答案 1 :(得分:1)

您开始撰写的测试似乎不只是测试AnnouncementsCtrl,还测试Announcements服务/工厂。在这种情况下的迹象是

  • 你并没有嘲笑Announcements服务/工厂/没有抄袭任何方法。
  • AnnouncementsCtrl中没有关于发出http请求的代码,但您在测试中使用$httpBackend.expect(...
  • 声称测试AnnouncementsCtrl的测试成功/失败将成功或失败,具体取决于Announcements服务/工厂中的代码。

这违背了通常用于单元测试的内容:单独测试每个组件。保持这个答案的焦点在测试成功回调传递给create返回的promise的then方法,我的建议是模拟Announcements服务/工厂,所以它的create方法返回一个promise你可以在测试中控制。这个模拟的形式如下:

var MockAnnouncement = null;
var deferred = null;
beforeEach(module(function($provide) {
  MockAnnouncement = function MockAnnouncement() {
    this.create = function() {
      return deferred.promise;
    };
  };
  $provide.value('Announcement', MockAnnouncement);
}));

然后,您必须确保在每次测试之前创建延迟对象:

beforeEach(inject(function($rootScope, $controller, $q) {
  $scope = $rootScope.$new();
  deferred = $q.defer(); // Used in MockAnnouncement 
  ctrl = $controller('AnnouncementsCtrl', {
    $scope: $scope
  });
}));

然后在测试中解析此延迟对象:

it('calls create and on success sets $scope.foo="bar"', function() {
  $scope.save();
  deferred.resolve();
  $scope.$apply();
  expect($scope.foo).toBe('bar');
});

稍微扩展一下,测试控制器的一些其他行为,可以在http://plnkr.co/edit/v1bCfmSPmmjBoq3pfDsk

看到