如何模拟一个角度 $http 调用并返回一个行为类似于 $http 的 promise 对象

Posted

技术标签:

【中文标题】如何模拟一个角度 $http 调用并返回一个行为类似于 $http 的 promise 对象【英文标题】:How to mock an angular $http call and return a promise object that behaves like $http 【发布时间】:2014-08-14 22:18:59 【问题描述】:

有没有办法返回一个 HttpPromise(或类似的东西)来模拟对 $http 的调用?我想设置一个全局变量来指示是否发出了真正的 HTTP 请求,或者是否返回了带有假数据的假 HttpPromise 对象。

比如我有一个类似这样的服务:

angular
  .module('myservice')
  .factory('MyService', ['$http', function($http) 
      return 
       get : function(itemId) 
         if (isInTestingMode) 
           // return a promise obj that returns success and fake data
         
         return $http.get("/myapp/items/" + itemId);
       
    ;
  ]);

在我的控制器中,我调用了与上述类似的服务:

        // Somewhere in my controller

        MyService.get($scope.itemId)
           .success(function(data) 
              $scope.item = data;
           )
           .error(function(data, status, headers, config) 
              $scope.notFound = true;
           );

我正在尝试更改控制器代码;我希望successerror 链接在我的“isInTestMode”中仍然有效。 是否可以按照我在服务中描述的方式伪造HttpPromise


下面是上述“MyService”的修订版(sn-p),在 promise 对象上包含 successerror。但是,我该如何执行success 方法呢?

        return 
           get : function(itemId) 
             if (isInTestingMode) 
                var promise = $.defer().promise;
                // Mimicking $http.get's success 
                promise.success = function(fn) 
                  promise.then(function() 
                     fn( itemId : "123", name : "ItemName", 200, , );
                  );
                  return promise;
                ;
                // Mimicking $http.get's error 
                promise.error = function(fn) 
                   promise.then(null, function(response) 
                     fn("Error", 404, , );
                   );
                   return promise;
                ;
                return promise;
             
             return $http.get("/myapp/items/" + itemId);
           
        

【问题讨论】:

【参考方案1】:

只需使用$qservice 的deferred 方法

    var fakeHttpCall = function(isSuccessful) 
    
      var deferred = $q.defer()
    
      if (isSuccessful === true) 
        deferred.resolve("Successfully resolved the fake $http call")
      
      else 
        deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
      
      
      return deferred.promise
    

然后你可以像$http 承诺一样调用你的函数(当然,你必须自定义你想要放入其中的任何内容)。

    fakeHttpCall(true).then(
      function (data) 
        // success callback
        console.log(data)
      ,
      function (err) 
        // error callback
        console.log(err)
      )

【讨论】:

我不明白如何强制执行成功或错误。我根据您的评论使用扩展示例更新了我的问题。我看不出如何从 MyService 的“get”中强制使用成功方法。 如果你想模拟成功,你只需要设置deferred.resolve(result)然后返回promise ....或者如果你想模拟失败,设置deferred.reject(error) 我很确定这不会起作用,因为deferred.promise 不提供success()error() 用于链接,例如$http。例如,请参阅此答案以了解如何实现这一目标 - ***.com/a/19747182/404099。 @IliaBarahovski success()error() 不再使用。 Promises 为您提供then( success, error ) 构造,它更强大,可以链接到无限。见docs.angularjs.org/api/ng/service/$q @IliaBarahovski 你是完全正确的,我说得对,但写错了:)【参考方案2】:

我发现this post 与我所问的类似。

但是,我想要一种方法来模拟我的服务调用,以便可以返回虚假数据,而不是发出真正的 HTTP 请求调用。对我来说,处理这种情况的最好方法是使用 angular 的$httpBackend 服务。例如,要绕过对我的“项目”资源的 GET 请求但绕过我的部分/模板的 GET,我会这样做:

angular
   .module('myApp', ['ngMockE2E'])
   .run(['$httpBackend', function($httpBackend) 
      $httpBackend
        .whenGET(/^partials\/.+/)
        .passThrough();
      $httpBackend
        .whenGET(/^\/myapp\/items\/.+/)
        .respond(itemId : "123", name : "ItemName");
]);

有关 $httpBackend 的更多信息,请参阅this documentation。

【讨论】:

【参考方案3】:

我终于found a way using jasmin。 $httpBackend 对我来说是没有选择的,因为我还需要在同一服务上模拟非 $http 方法。我还认为需要指定 url 的控制器测试并不完美,因为控制器和它的测试不需要知道它。

这是它的工作原理:

beforeEach(inject(function ($controller, $rootScope, $q) 
  scope = $rootScope.$new();
  mockSvc = 
    someFn: function () 
    ,
    someHttpFn: function () 
    
  ;

  // use jasmin to fake $http promise response
  spyOn(mockSvc, 'someHttpFn').and.callFake(function () 
    return 
      success: function (callback) 
        callback(
         // some fake response
        );
      ,
      then: function(callback) 
         callback(
         // some fake response, you probably would want that to be
         // the same as for success 
         );
      ,
      error: function(callback)
        callback(
         // some fake response
        );             
      
    
  );

  MyCtrl = $controller('MyCtrl', 
    $scope: scope,
    MyActualSvc: mockSvc
  );
));

【讨论】:

【参考方案4】:

你可以实现你的 FakeHttp 类:

var FakeHttp = function (promise) 
    this.promise = promise;
    this.onSuccess = function();
    this.onError = function();
    this.premise.then(this.onSuccess, this.onError);
;
FakeHttp.prototype.success = function (callback) 
    this.onSuccess = callback;
    /**You need this to avoid calling previous tasks**/
    this.promise.$$state.pending = null;
    this.promise.then(this.onSucess, this.onError);
    return this;
;
FakeHttp.prototype.error = function (callback) 
    this.onError = callback;
    /**You need this to avoid calling previous tasks**/
    this.promise.$$state.pending = null;
    this.promise.then(this.onSuccess, this.onError);
    return this;
;

然后在你的代码中,你会从 promise 中返回一个新的 fakeHttp。

if(testingMode)
    return new FakeHttp(promise);
;

promise 必须是异步的,否则将不起作用。为此,您可以使用 $timeout。

【讨论】:

【参考方案5】:

简单易懂!

您可以像这样使用angular-mocks-async 来做到这一点:

var app = ng.module( 'mockApp', [
    'ngMockE2E',
    'ngMockE2EAsync'
]);

app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) 

    $httpBackend.whenAsync(
        'GET',
        new RegExp( 'http://api.example.com/user/.+$' )
    ).respond( function( method, url, data, config ) 

        var re = /.*\/user\/(\w+)/;
        var userId = parseInt(url.replace(re, '$1'), 10);

        var response = $q.defer();

        setTimeout( function() 

            var data = 
                userId: userId
            ;
            response.resolve( [ 200, "mock response", data ] );

        , 1000 );

        return response.promise;

    );

]);

【讨论】:

以上是关于如何模拟一个角度 $http 调用并返回一个行为类似于 $http 的 promise 对象的主要内容,如果未能解决你的问题,请参考以下文章

如何以角度读取 Http 响应标头

模拟服务以调用本地 json-server 而不是远程端点 - 测试语法? (角度 2)

如何使用PowerMock调用私有方法并获取返回值?

如何从 C++ 基类继承抽象行为

如何使用 mockito 模拟方法?

Flutter 如何模拟对 rootBundle.loadString(...) 的调用,然后重置模拟的行为?