【问题标题】:Best way to use Jasmine to test Angular Controller calls to Services with Promise return使用 Jasmine 测试 Angular 控制器调用服务的最佳方法,并返回 Promise
【发布时间】:2016-03-28 16:43:38
【问题描述】:

在寻找一个好的答案/样本一周后,我决定发布我的问题。

我需要知道如何最好地编写和测试这样的东西:

控制器

// my.controller.js
(function () {

  'use strict';

  angular.module('myApp.myModule').controller('Awesome', Awesome);

  function Awesome($http, $state, AwesomeService) {

    var vm = this; // using 'controllerAs' style

    vm.init = init;
    vm.awesomeThingToDo = awesomeThingToDo;

    vm.init();

    function awesomeThingToDo() {
      AwesomeService.awesomeThingToDo().then(function (data) {
        vm.awesomeMessage = data.awesomeMessage;
      });
    }

    function init() {
        vm.awesomeThingToDo(); // Should be ready on page start
    }
  }
})();

服务

// my.service.js
(function () {
  'use strict';

  angular.module('myApp.myModule').factory('AwesomeService', AwesomeService);

  function AwesomeService($resource, $http) {

    var service = {
      awesomeThingToDo: awesomeThingToDo
    }

    return service;

    function awesomeThingToDo() {

      var promise = $http.get("/my-backend/api/awesome").then(function (response) {
          return response.data;
        });

      return promise;
    }
  }
})();

我的应用程序在这种结构下运行良好。我的服务单元测试也可以。 但我不知道如何在 Controller 上进行单元测试。

我尝试过这样的事情:

规格

// my.controller.spec.js
(function () {
  'use strict';

  describe("Awesome Controller Tests", function() {

    beforeEach(module('myApp.myModule'));

    var vm, awesomeServiceMock;

    beforeEach(function () {
      awesomeServiceMock = { Is this a good (or the best) way to mock the service?
        awesomeThingToDo: function() {
          return {
            then: function() {}
          }
        }
      };
    });

    beforeEach(inject(function ($controller) {
      vm = $controller('Awesome', {AwesomeService : awesomeServiceMock});
    }));

    it("Should return an awesome message", function () {
      // I don't know another way do to it... :(
      spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function() {
        return {
          then: function() {
            vm.awesomeMessage = 'It is awesome!'; // <-- I think I shouldn't do this.
          }
        }
      });

      vm.awesomeThingToDo(); // Call to real controller method which should call the mock service method.

      expect(vm.awesomeMessage).toEqual('It is awesome!'); // It works. But ONLY because I wrote the vm.awesomeMessage above.

    });

  });
})();

我的应用使用 Angular 1.2.28 和 Jasmine 2.1.3(带有 Grunt 和 Karma)。

更新:已解决!

it("Should return an awesome message", function () {
  // Solved with callback parameter
  spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
    return {
      then: function(callback) {
        callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
      }
    }
  });

【问题讨论】:

    标签: angularjs testing mocking jasmine promise


    【解决方案1】:

    我用一个可能的(坏的)解决方案更新了这个问题:

    it("Should return an awesome message", function () {
      // Solved with callback parameter
      spyOn(awesomeServiceMock, "awesomeThingToDo").and.callFake(function(callback) {
        return {
          then: function(callback) {
            callback({awesomeMessage: 'It is awesome!'}); //callback call works fine! :D
          }
        }
      });
    

    我使用回调来传递模拟参数并调用真正的实现。 :D

    【讨论】:

      【解决方案2】:

      不,我不会这样做。

      首先,无需创建模拟服务:您可以注入真实的服务,然后对其进行监视。

      其次,Angular 拥有创建 Promise 和解决 Promise 所需的一切。无需使用伪造的then() 函数创建伪造的对象。

      我会这样做:

      describe("Awesome Controller Tests", function() {
      
          beforeEach(module('myApp.myModule'));
      
          var vm, awesomeService, $q, $rootScope;
      
          beforeEach(inject(function($controller, _awesomeService_, _$q_, _$rootScope_) {
            $q = _$q_;
            awesomeService = _awesomeService_;
            $rootScope = _$rootScope_;
            vm = $controller('Awesome');
          }));
      
          it("Should return an awesome message", function () {
            spyOn(awesomeService, "awesomeThingToDo").and.returnValue(
                $q.when({
                  awesomeMessage: 'awesome message'
                }));
      
            vm.awesomeThingToDo(); 
      
            // at this time, the then() callback hasn't been called yet: 
            // it's called at the next digest loop, that we will trigger
      
            $rootScope.$apply();
      
            // now the then() callback should have been called and initialized 
            // the message in the controller with the message of the promise
            // returned by the service
      
            expect(vm.awesomeMessage).toBe('awesome message');
          });
      
      });
      

      无关说明:1.2.28 已经很老了。您应该迁移到最新版本。

      【讨论】:

      • 我会更新 Angular 版本...但我现在做不到。 :( 但我用解决方案更新了问题。:D 谢谢!
      • 您的解决方案既过于冗长,也不是最优的:promise 应该是异步的,但您的测试使其同步。您应该更喜欢我的回答中解释的基于承诺的解决方案。
      • 你是对的。我的解决方案不够好。我尝试应用您的解决方案,注入服务,使用 $q... 但我收到此错误:错误:意外请求:GET /my-backend/api/awesome
      • 这意味着要么你没有窥探到服务方法,要么在你窥探之前调用了服务方法。如果您发布的是真实代码,并且您按原样使用了我的测试代码,我看不出这是怎么发生的。如果没有,请发布您尝试过的实际代码。
      • 这只是一个示例代码。真实代码有一点不同:在init()上调用了一些控制器方法。其中一些方法调用服务方法。问题再次更新。 ;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-03
      • 1970-01-01
      • 2013-11-12
      • 2014-11-22
      • 2018-10-25
      • 2023-03-17
      • 1970-01-01
      相关资源
      最近更新 更多