0

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 Answers 3

1

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:)

Sign up to request clarification or add additional context in comments.

Comments

0

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

});

2 Comments

Hm ... this way Loader returns a promise making my API too complicated. I need it to return the actual data. I've seen the same trick works in Controllers - $scope.result = data inside the callback of $http().success. I just can't get it work with factory.
Somehow I still can't get it work. I don't see console.log(result) executed. Also I've tried to put mydata = result right after, yet mydata remained undefined
0

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.

3 Comments

actually, good odds this won't work either because of the guarantees provided by promises. I couldn't find much info on $q, except that it's inspired by Q... Basically Angular has no support for async tests, so testing async things is going to be flaky...
Just tested and it didn't log anything. Also tested with assertion data = result; which wasn't executed. Very confusing.
so, I can't be certain without actually running this stuff in a debugger, but one of the guarantees with promises is that a callback registered with then will not be invoked until the current call stack has been cleared. So those functions could not be invoked until after your (synchronous) tests have completed, which is useless to you. Hence the need for async tests. I'm uncertain because I don't know how $q actually acts...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.