【问题标题】:How to mock an angular $http call and return a promise object that behaves like $http如何模拟一个角度 $http 调用并返回一个行为类似于 $http 的 promise 对象
【发布时间】: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);
           }
        }

【问题讨论】:

    标签: angularjs angularjs-http angular-mock


    【解决方案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。例如,请参阅此答案以了解如何实现这一目标 - stackoverflow.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;
          
              });
          
          }]);
          

          【讨论】:

            猜你喜欢
            • 2020-12-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-08-03
            • 2016-10-20
            • 2022-12-03
            • 1970-01-01
            相关资源
            最近更新 更多