Chris Green Chris Green - 11 months ago 46
AngularJS Question

AngularJS Unit Testing: Attaching Data from $q.resolve() to object

I'm testing a service that uses another service for API calls, let's call this the data service. The data service is tested elsewhere, so I've abstracted it away with a simple implementation that contains empty functions; I'm returning data via a deferred object and Jasmine's spyOn syntax.

The trouble I'm finding with this approach is when the data is returned, it's not immediately available on the calling object, as it would be if I used $httpBackend. Aware I could just use $httpBackend, but I'd like to know if I've missed something (simple or otherwise) in this approach.

Example section of code I'm trying to test:

storeTheData = dataService.getSomeData();
storeTheData.$promise.then(function(data) {

/*this would work*/

/*but this would not, when testing using $q*/
_.forEach(storeTheData, function(storedData) {
/*do something with each object returned*/

As a side note, I don't think the situation is helped by the ...$promise.then on another line, but ideally I wouldn't change the code (I'm providing test coverage to something written a while ago...)

Example of the test:

dataService = {
getSomeData: function () { }
getSomeDataDeferred = $q.defer();
spyOn(dataService, "getSomeData").and.returnValue({$promise: getSomeDataDeferred.promise});

getSomeDataDeferred.resolve([{obj: "obj1"}, {obj: "obj2"}]);

With the test described above, the console.log(data) would be testable as the data is accessible from being passed into the .then(). But the data is not immediately available from storeTheData, so storeTheData[0].obj would be undefined. On debug, I can see the data if I go through the promise that was attached to storeTheData via storeTheData.$$state.value

Like I said, I know I could use $httpBackend instead, but is there any way to do this with $q without changing the code under test?

Answer Source

I've not found a way to do this with $q.resolve, but I do have a solution that doesn't involve using the data service or changing the code under test. This is as good, because the main things I wanted to avoid were testing the data service as a side effect and changing the code.

My solution was to create a $resource object via $injector...

$resource = $inject.get("$resource");

...then return that in my basic implementation of the data service. This means I could use $httpBackend to respond to the request to an end point that isn't reliant on the data service's definition staying consistent.

dataService = {
    getSomeData: function () {
        /* new code starts here */
        var resource = $resource(null, null, {
            get: {
                method: "GET",
                isArray: true,
                url: "/getSomeData"

        return resource.get();
       /* new code ends here */
$httpBackend.when("GET", "/getSomeData").respond(...;