如何模拟一个 Angular 的 $http 调用并返回一个表现类似于 $http 的 promise 对象

19

有没有一种方法可以返回一个 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;
           });
我试图不改变控制器代码;当在“isInTestMode”时,我希望“success”和“error”链接仍然可以工作。是否可能以我在服务中描述的方式伪造一个“HttpPromise”?
以下是上面“MyService”的修订版(片段),包含 promise 对象上的“success”和“error”。但是,如何执行“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);
           }
        }
5个回答

18

只需使用$q服务的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 promise 一样调用你的函数(当然,你需要自定义它里面的任何内容)。

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

我不理解如何强制执行成功或错误。根据您的评论,我更新了我的问题,并提供了一个扩展示例。我不知道如何从MyService中的“get”方法内部强制执行成功方法。 - whyceewhite
你只需要设置 deferred.resolve(result),然后返回 Promise,如果你想模拟成功... 或者设置 deferred.reject(error) 如果你想模拟失败。 - KnF
4
我很确定这行代码不会起作用,因为deferred.promise没有像$http一样提供success()error()来进行链式调用。请参考此答案的示例以了解如何实现 - https://dev59.com/1GIj5IYBdhLWcg3w8JNZ#19747182。 - Ilia Barahovsky
2
@IliaBarahovski success()error()已经不再使用。 Promises提供了更强大的then(success, error)结构,可以无限链接。 请参阅https://docs.angularjs.org/api/ng/service/$q - domokun
@domokun 可能我表达不够清楚。在答案中,第二部分展示了对 fakeHttpCall(true).success() 的调用。这应该会失败,因为 fakeHttpCall 返回的是 deferred.promise。后者确实定义了 then(),正如您在评论中提到的那样,但没有定义 success() - Ilia Barahovsky
1
@IliaBarahovski,你说得完全正确,我在讲道理但写错了 :) - domokun

6
我发现这篇帖子与我的问题相似。
不过,我希望找到一种方法来模拟服务调用,以便返回虚假数据而不是真正的HTTP请求调用。对于我来说,处理这种情况的最佳方式是使用Angular的$httpBackend服务。例如,要绕过对“items”资源的GET请求,但不要绕过我的部分/模板的GET请求,我会这样做:
angular
   .module('myApp', ['ngMockE2E'])
   .run(['$httpBackend', function($httpBackend) {
      $httpBackend
        .whenGET(/^partials\/.+/)
        .passThrough();
      $httpBackend
        .whenGET(/^\/myapp\/items\/.+/)
        .respond({itemId : "123", name : "ItemName"});
}]);

欲了解更多关于$httpBackend的信息,请参见此文档


4

我最终找到了使用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
  });
}));

0

你可以实现你的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);
};

承诺必须是异步的,否则它将无法工作。为此,您可以使用$timeout。

0

非常简单!

您可以使用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;

    });

}]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接