【问题标题】:testing Angular async services with Jasmine使用 Jasmine 测试 Angular 异步服务
【发布时间】:2014-09-13 18:50:11
【问题描述】:

我正在尝试使用 Jasmine 测试一个真正的 http 调用(集成测试),但是当我调用一个使用 $http.get 的方法时,它会超时并且服务器永远不会被调用。

我知道我应该注入 $http 的实现,但不确定应该在哪里进行。

searchSvc

app.service('searchSvc', ['$http', '$q', searchSvc]);
function searchSvc($http, $q) {
  return {
    search: function(text) {
      console.log('svc.search called with ', text); // this does get called 
      return $q.when($http.get('/search/' + text));
    }
  };
}

搜索规范

describe("searchTest", function() {
  var ctrl, svc, $http;

  beforeEach(function () {
      module('testApp');
    inject(function(_$controller_, searchSvc, _$http_){
      ctrl = _$controller_('searchCtrl');
      svc = searchSvc;
      $http = _$http_;
    })
  });

  it('test server search', function(done) {
      svc.search('re').then(function(result) {
        console.log('promise then'); // this never gets called, because server never gets called
        expect(result).not.toBeNull();
        expect(result.data).not.toBeNull();
        expect(result.data.length).toBeGreaterThan(0);

        done();
      });
  });

【问题讨论】:

  • 我不确定它是否允许你这样做......这不是真正的单元测试。这是一个集成测试。我相信这个想法是测试使用$httpBackend mock 调用服务器的代码,并使用自己的单元测试测试服务器本身,最后编写端到端测试以确保它们都能正常工作。这些将使用量角器运行并在没有模拟的情况下测试实际应用程序。
  • 对,我从来没有说过这是一个单元测试。我正在尝试使用 Jasmine 进行集成测试以及单元测试
  • 嗯。我懂了。现在我也很好奇。我删除了 angular-mocks,从一个实际的 angular 模块中引用了 $http 并尝试了它,它也永远不会解决这个承诺。我认为在正常运行的应用程序内部应该发生的事情不会在这里发生。有点像我们必须在测试中手动调用$scope.$digest
  • 我猜这与依赖注入的工作方式有关?我见过带有控制器的示例,您可以在其中提供 Jasmine 块内的依赖项实现,但找不到服务的表示法

标签: angularjs jasmine integration-testing


【解决方案1】:

如果你使用 Promise,你可以在这里找到如何处理它们 http://entwicklertagebuch.com/blog/2013/10/how-to-handle-angularjs-promises-in-jasmine-unit-tests/

【讨论】:

  • 嗯我不确定这是否是一个承诺问题,因为触发了 searchSvc.search。问题是服务器永远不会被调用
【解决方案2】:

这是一种假设,但如果您同时包含 ngMockngMockE2E 模块作为您的应用程序模块的依赖项(ngMock 需要在依赖项列表中的 ngMockE2E 之前出现)您应该能够使用 @ 987654325@ 服务由ngMockE2E 模块提供给passThrough 对测试规范中实际后端的搜索api 调用。

试试这样的方法,看看它是否有效:

describe("searchTest", function() {
  var ctrl, svc, $httpBackend;

  beforeEach(function () {
    module('testApp');
    inject(function(_$controller_, searchSvc, _$httpBackend_){
      $httpBackend = _$httpBackend_;
      ctrl = _$controller_('searchCtrl');
      svc = searchSvc;
    });
  });

  it('test server search', function(done) {
      $httpBackend.whenGET(/^\/search\//).passThrough();
      svc.search('re').then(function(result) {
        console.log('promise then'); // this never gets called, because server never gets called
        expect(result).not.toBeNull();
        expect(result.data).not.toBeNull();
        expect(result.data.length).toBeGreaterThan(0);

        done();
      });
  });
});

【讨论】:

    【解决方案3】:

    这是我在使用 ngMock 进行单元测试时用来进行真正的 HTTP 调用的解决方案。我主要用它来调试、完成测试、获取 JSON 示例等。

    我在我的博客上写了一篇关于解决方案的更详细的帖子:How to Unit Test with real HTTP calls using ngMockE2E & passThrough

    解决方法如下:

    angular.mock.http = {};
    
    angular.mock.http.init = function() {
    
      angular.module('ngMock', ['ng', 'ngMockE2E']).provider({
        $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
        $log: angular.mock.$LogProvider,
        $interval: angular.mock.$IntervalProvider,
        $rootElement: angular.mock.$RootElementProvider
      }).config(['$provide', function($provide) {
        $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
        $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
        $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
        $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
        $provide.decorator('$controller', angular.mock.$ControllerDecorator);
      }]);
    
    };
    
    angular.mock.http.reset = function() {
    
      angular.module('ngMock', ['ng']).provider({
        $browser: angular.mock.$BrowserProvider,
        $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
        $log: angular.mock.$LogProvider,
        $interval: angular.mock.$IntervalProvider,
        $httpBackend: angular.mock.$HttpBackendProvider,
        $rootElement: angular.mock.$RootElementProvider
      }).config(['$provide', function($provide) {
        $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
        $provide.decorator('$$rAF', angular.mock.$RAFDecorator);
        $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
        $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
        $provide.decorator('$controller', angular.mock.$ControllerDecorator);
      }]);
    
    };
    

    在 ngMock 之后包含这个源文件,例如:

    <script type="text/javascript" src="angular.js"></script>
    <script type="text/javascript" src="angular-mocks.js"></script>
    <!-- this would be the source code just provided -->
    <script type="text/javascript" src="ngMockHttp.js"></script>
    

    如何编写测试?

      describe('http tests', function () {
    
        beforeEach(module('moviesApp'));
    
        var $controller;
        var $httpBackend;
        var $scope;
    
        describe('real http tests', function() {
    
          beforeEach(angular.mock.http.init);
          afterEach(angular.mock.http.reset);
    
          beforeEach(inject(function(_$controller_, _$httpBackend_) {
            $controller = _$controller_;
            $scope = {};
            $httpBackend = _$httpBackend_;
    
            // Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
            $httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
          }));
    
          it('should load default movies (with real http request)', function (done) {
            var moviesController = $controller('MovieController', { $scope: $scope });
    
            setTimeout(function() {
              expect($scope.movies).not.toEqual([]);
              done();
            }, 1000);
    
          });
    
        });
    
      });
    

    它是如何工作的?

    它使用 ngMockE2E 的 $httpBackEndProvider 版本,它为我们提供了我们在测试中看到的 passThrough 函数。顾名思义,这可以让原生 HTTP 调用通过。

    我们需要重新定义 ngMock 模块,不包含 $BrowserProvider 的虚假版本,因为这会阻止在使用 ngMock 的单元测试中进行真正的 HTTP 调用。

    【讨论】:

    • 嗯,谢谢你发布这个!我仍然感到震惊的是,在茉莉花中进行端到端测试是如此困难。我一定会尝试你的方法并报告回来。再次感谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多