0
votes

I am trying to test very simple AngularJS Service loading JSON data:

angular.module('archive', [])
  .factory('Loader', function ($http) {
    var result;
    $http.get('path').success(function(data) {
      result = data;
      console.log('returning: ', result);
    });
    return {
      getData: result
    }
  });

Here is my test:

describe('TestArchive', function () {
  beforeEach(module('archive'));

  it('should load data', inject(function(Loader, $httpBackend){
    $httpBackend
      .whenGET('path')
      .respond(22);
    var result = Loader.getData;
    $httpBackend.flush();
    console.log(result);
  }));

});

I was expecting to see 22 loaded but as I see from console, it does not happen and result is undefined. Any idea what is wrong?

3

3 Answers

1
votes

In the service definition, you are basically saying:

var result;
$http.get().success(function(data) {
    result = data; // will be executed after below 'return'
}
return result;

Which means result will be undefined when returned, because of the async http call.

A better approach would be to return a function that returns the actual result:

return {
    getData: function() { return result; }
};

But be aware that, even in this approach, you might call getData() too soon (before the http request had a chance to finish).

Finally, the fail-proof way is to just return a promise, manually created (as suggested by @dimirc), or simply the promise returned by $http.get() itself:

return {
    getData: $http.get('path')
};

Also, be aware that respond() likes the response data to be a string, as it can also accept an http status (number) as the first param:

$httpBackend.whenGET('path').respond(200, '22');

It might work with a number as the single param, but it's always better to clearly state your intentions:)

0
votes

You should take in consideration that the callback from .success is called asynchronously, so we can use a promise to better handle the result.

angular.module('archive', [])
  .factory('Loader', function ($http, $q) {
    var result;
    var deferred = $q.defer();
    $http.get('path').success(function(data) {
      deferred.resolve(data);
      console.log('returning: ', data);
    });
    return {
      getData: deferred.promise
    }
  });

And the test

describe('TestArchive', function () {
  beforeEach(module('archive'));

  it('should load data', inject(function(Loader, $httpBackend){
    $httpBackend
      .whenGET('path')
      .respond(22);
    var promise = Loader.getData;
    promise.then(function(result){
      console.log(result);
    })
    $httpBackend.flush();
  }));

});
0
votes

warning : I don't really know angular.

however...

angular.module('archive', [])
  .factory('Loader', function ($http) {
    return $http.get('path')
      .then(function(result){
        console.log('returning: ', result);
        return result;
      });
});

and the test :

describe('TestArchive', function () {
  beforeEach(module('archive'));

  it('should load data', inject(function(Loader, $httpBackend){
    $httpBackend
      .whenGET('path')
      .respond(22);
    var resultProm = Loader;
    resultProm.then(function(result){
      console.log(result);
      // maybe add an assertion here?
    });
    $httpBackend.flush();
  }));
});

You won't be able to return data directly from your loader directive, so I'm not really sure this test makes sense... It should call both of the console.logs though.

It looks as though Angular has tried to avoid asynchronous tests entirely, so it uses $httpBackend.flush as a kind of trampoline in the tests.