How do I mock the result in a $http.get promise when testing my AngularJS controller?

20,141

I don't think $httpBackend is what you're after here, you want the whole factory to be mocked without it having a dependency on $http?

Take a look at $q, in particular the code sample under the Testing header. Your issue might be resolved with code that looks like this:

'use strict';

describe('mocking the factory response', function () {

    beforeEach(module('myApp.controllers'));

    var scope, fakeFactory, controller, q, deferred;

    //Prepare the fake factory
    beforeEach(function () {
        fakeFactory = {
            requestPeople: function () {
                deferred = q.defer();
                // Place the fake return object here
                deferred.resolve({ "one": "three" });
                return deferred.promise;
            }
        };
        spyOn(fakeFactory, 'requestPeople').andCallThrough();
    });

    //Inject fake factory into controller
    beforeEach(inject(function ($rootScope, $controller, $q) {
        scope = $rootScope.$new();
        q = $q;
        controller = $controller('MyCtrl1', { $scope: scope, MyFactory: fakeFactory });
    }));

    it('The peopleList object is not defined yet', function () {
        // Before $apply is called the promise hasn't resolved
        expect(scope.peopleList).not.toBeDefined();
    });

    it('Applying the scope causes it to be defined', function () {
        // This propagates the changes to the models
        // This happens itself when you're on a web page, but not in a unit test framework
        scope.$apply();
        expect(scope.peopleList).toBeDefined();
    });

    it('Ensure that the method was invoked', function () {
        scope.$apply();
        expect(fakeFactory.requestPeople).toHaveBeenCalled();
    });

    it('Check the value returned', function () {
        scope.$apply();
        expect(scope.peopleList).toBe({ "one": "three" });
    });
});

I've added some tests around what $apply does, I didn't know that until I started playing with this!

Gog

Share:
20,141
Mendhak
Author by

Mendhak

Updated on July 11, 2020

Comments

  • Mendhak
    Mendhak almost 4 years

    After much reading, it seems that the recommended way to call a web service from an AngularJS controller is to use a factory and return a promise from that.

    Here I have a simple factory which calls a sample API.

    myApp.factory('MyFactory', ['$http',function($http) {
    var people = {
            requestPeople: function(x) {
                var url = 'js/test.json';
                return $http.get(url);
            }
        };
    return people;
    }]);
    

    And this is how I call it in the controller

    myApp.controller('MyCtrl1', ['$scope', 'MyFactory', function ($scope, MyFactory) {
            MyFactory.requestPeople(22).then(function(result) {
                 $scope.peopleList = result;
            });
    }]);
    

    While it works fine, I would like to be able to mock the result that is passed in when then is called. Is this possible?

    My attempt so far has produced nothing. This is my attempt:

    //Fake service
    var mockService = {
        requestPeople: function () {
            return {
                then: function () {
                    return {"one":"three"};
                }
            }
    
        }
    };
    
    
    //Some setup
    beforeEach(module('myApp.controllers'));
    var ctrl, scope;
    
    beforeEach(inject(function ($rootScope, $controller) {
        scope = $rootScope.$new();
    
        ctrl = $controller('MyCtrl1', { $scope: scope, MyFactory: mockService });
    }));
    
    //Test
    it('Event Types Empty should default to false', inject(function () {
        expect(scope.peopleList.one).toBe('three');
    }));
    

    The error that I get when running this in karma runner, is

    TypeError: 'undefined' is not an object (evaluating 'scope.peopleList.one')

    How can I get this test working with my mocked data?