How to mock an angular $http call and return a promise object that behaves like $http
Asked Answered
I

5

19

Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.

For example, I have a service that is similar to this:

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

And in my controller, I have a call to the aforementioned service that looks similar to this:

        // Somewhere in my controller

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

I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode". Is it possible to fake an HttpPromise in the way that I described in the service?


Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?

        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);
           }
        }
Imprudent answered 24/6, 2014 at 15:11 Comment(0)
I
6

I found that this post is similar to what I was asking.

However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:

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

See this documentation for more information on $httpBackend.

Imprudent answered 24/6, 2014 at 21:46 Comment(0)
C
18

Just use the deferred method of the $qservice

    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
    }

And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).

    fakeHttpCall(true).then(
      function (data) {
        // success callback
        console.log(data)
      },
      function (err) {
        // error callback
        console.log(err)
      })
Corbel answered 24/6, 2014 at 15:26 Comment(6)
I'm not understanding how the success or error is forced to execute. I updated my question with an expanded example based on your comment. I don't see how I could force the success method from within the "get" in the MyService.Imprudent
you just need to set the deferred.resolve(result) and then return the promise if you want to simulate the success.... or set deferred.reject(error) if you want to simulate a failureAureolin
I'm pretty sure this won't work as deferred.promise doesn't provide success() and error() for chaining, like $http. See this answer for example for how to achieve that - https://mcmap.net/q/666918/-declaring-a-promise-in-angularjs-with-named-success-error-callbacks.Blockus
@IliaBarahovski success() and error() are not used anymore. Promises gives you the then( success, error ) construct, which is more powerful and can be chained to infinite. See docs.angularjs.org/api/ng/service/$qCorbel
@Corbel probably I wasn't clear enough. In the answer the second part shows call to fakeHttpCall(true).success(). This should fail, since fakeHttpCall returns deferred.promise. The later indeed defines then(), as you mentioned in the comment, but not success().Blockus
@IliaBarahovski you are totally right, I was preaching right but writing wrong :)Corbel
I
6

I found that this post is similar to what I was asking.

However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:

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

See this documentation for more information on $httpBackend.

Imprudent answered 24/6, 2014 at 21:46 Comment(0)
I
4

I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.

Here is how it works:

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
  });
}));
Inhaul answered 4/5, 2015 at 10:7 Comment(0)
D
0

You can implement your FakeHttp class:

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

Then in your code, you would return a new fakeHttp out of the promise.

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

The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.

Dejesus answered 16/2, 2016 at 10:25 Comment(0)
D
0

easy peasy!

You can do it using angular-mocks-async like so:

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;

    });

}]);
Disposed answered 14/9, 2016 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.