Ryan Ferretti
Ryan Ferretti

Reputation: 2961

AngularJS : How to properly mock a Promise returned from $http

I have been fighting with this for a little bit now and need some guidance. I would like to unit test this angular service... specifically, the failed part of the promise.

(function () {
angular.module('testable')
    .factory('myService', ["$http", "$q", function ($http, $q) {
        return {
            createThing: function(thing) {
                return $http.post("//...", thing)
                        .then(function (response) {
                            return response.data;
                        }, function (response) {
                            return $q.reject(response.statusText);
                        });
            }
        };
    }]);
}());

I have been through a number of SO posts about this but each one seems to be a little different. First of all, if any of my service code is not correct in setting up the promise, please stop me there. I have been through about 10 different iterations of testing a rejected promise but nothing is working. Here is what I have:

    beforeEach(inject(function ($injector,myService) {
        sut = myService;
        $httpBackend = $injector.get('$httpBackend');
        $q = $injector.get('$q');
        $rootScope = $injector.get('$rootScope');
        dataToSend = "send me!";
        deferred = $q.defer();
    }));

    it("should get error on error", function () {
        var result,
            expected = "FAIL!!!!!";

        deferred.reject(expected);
        $httpBackend.expectPOST(testUrl,dataToSend).respond(deferred.promise);

        sut.createThing(dataToSend).then(function (returnFromPromise) {
            result = returnFromPromise;
        });

        $rootScope.$apply();
        $httpBackend.flush();

        // expect something here but result is always wrong
    });

I understand that promises run asynchronously... and I have been cobbling together tidbits of info, but does anyone have advice for completing this unit test?

Upvotes: 3

Views: 2171

Answers (1)

Michael Benford
Michael Benford

Reputation: 14104

When you use the $httpBackend service you don't need to work with promises in order to simulate HTTP requests. In fact, the respond() method has the following signature:

respond: function(status, data, headers, statusText) { 
  ...
}

To simulate an error all you have to do is return a non-success status code (!= 200):

it('should not create a thing', function() {
    var result;

    // Arrange
    $httpBackend.expectPOST('https://domain.com/api')
      .respond(500, null, null, 'some error');

    // Act
    myService.createThing('foo').then(angular.noop, function(data) {
      result = data;
    });

    $httpBackend.flush();

    // Assert
    expect(result).toBe('some error');
  });

And here's the whole spec covering both success and error cases:

describe('Testing a factory', function() {
  var myService, $httpBackend;

  beforeEach(function() {
    module('plunker');

    inject(function(_$httpBackend_, _myService_) {
      $httpBackend = _$httpBackend_;
      myService = _myService_;
    })
  });

  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('should create a thing', function() {
    var result;

    // Arrange
    $httpBackend.expectPOST('https://domain.com/api')
  .respond(200, 'some data');

    // Act
    myService.createThing('foo').then(function(data) {
      result = data;
    });

    $httpBackend.flush();

    // Assert
    expect(result).toBe('some data');
  });

  it('should not create a thing', function() {
    var result;

    // Arrange
    $httpBackend.expectPOST('https://domain.com/api')
  .respond(500, null, null, 'some error');

    // Act
    myService.createThing('foo').then(angular.noop, function(data) {
      result = data;
    });

    $httpBackend.flush();

    // Assert
    expect(result).toBe('some error');
  });
});

Working Plunker

Notice that there's no need to call $rootScope.$apply() here, because no digest cycle is needed to update anything.

Upvotes: 3

Related Questions