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

angularjs, jasmine, javascript, mocking, unit-testing

Solution

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

Problem

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?

Original source