Как высмеивать вызов angular $http и возвращать объект обещания, который ведет себя как $http

Есть ли способ вернуть 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;
           });

Я пытаюсь изменить не код контроллера; Я хочу, чтобы цепочка success и error продолжала работать, когда в моем "isInTestMode". Можно ли подделать HttpPromise так, как я описал в сервисе?


Ниже приведено пересмотренное издание "MyService" выше (фрагмент), содержащий success и error на объекте обещания. Но как мне выполнить метод success?

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

Ответы

Ответ 1

Я обнаружил, что этот пост похож на то, что я просил.

Тем не менее, мне нужен способ издеваться над моим сервисным вызовом, чтобы можно было вернуть ложные данные вместо того, чтобы выдавать истинный запрос HTTP-запроса. Лучший способ справиться с этой ситуацией, для меня - использовать службу angular $httpBackend. Например, чтобы обойти запрос GET для моего ресурса "items", но чтобы не обходить GET моих партикулов/шаблонов, я бы сделал что-то вроде этого:

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

Подробнее о $httpBackend см. эту документацию.

Ответ 2

Просто используйте метод deferred службы $q

    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 you fake $http call")
      }

      return deferred.promise
    }

И тогда вы можете назвать свою функцию как обещание $http (вам нужно, конечно, настроить все, что вы хотите поместить внутри нее).

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

Ответ 3

Наконец, нашел способ использования jasmin. $httpBackend для меня не было выбора, так как были также не $http-методы, которые мне нужны были в одной службе. Я также думаю, что проверка контроллера, требующая указания URL-адреса, не идеальна, поскольку imho контроллер и его тест не должны знать об этом.

Вот как это работает:

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;
};

Затем в вашем коде вы должны вернуть новый fakeHttp из обещания.

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

Обещание должно быть асинхронным, иначе это не сработает. Для этого вы можете использовать $timeout.

Ответ 5

легкий peasy!

Вы можете сделать это, используя 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;

    });

}]);