0

This is my controller:

angular.module("AuthenticationApp", ["BaseApp"])
    .controller("AuthCtrl", ["$http", "BaseService", function($http, BaseService) {
        var self = this;

        BaseService.fetch.stuffs()
          .then(function(data) {
            self.stuffs = data;
        });

    }]);

This my BaseService (the factory I need to mock):

angular.module("BaseApp", ["ngRoute"])

    .factory("BaseService", ["$http", "$q", function($http, $q) {
        var self = this;

        self.fetch = {
         stuffs: function() {
           return $http.get("/api/stuffs/")
             .then(function(response) {
                 return response.data;
           });
         }
        };    

        return self;
    }]);

I have no idea where to start. What I tried was this:

describe('Controller: AuthCtrl', function() {
    var ctrl, mockBaseService;

    beforeEach(function() {

        mockBaseService = {
            stuffs: 'test',
            fetch: { 
                stuffs: function() {
                    return {
                      then: function() {
                          return {stuffs: "stuffs"};
                      },
                    }
                },
            },
        };

        spyOn(mockBaseService.fetch, 'stuffs');

        module('BaseApp', function($provide) {
            $provide.value('BaseService', mockBaseService);
        });

        module('AuthenticationApp');

        inject(function($controller) {
            ctrl = $controller('AuthCtrl', {
            });
        });
    });

    it('BaseService.fetch.stuffs should be called on page load', function() {
        expect(mockBaseService.fetch.stuffs).toHaveBeenCalled();
        expect(ctrl.stuffs).toBe({stuffs: "stuffs"});
    });

});

But when I test this code, I get an error saying:

TypeError: Cannot read property 'then' of undefined
    at new <anonymous> (/home/user/Documents/ebdjango/ebdjangoapp/static/js/home.js:15:11)

home.js line 15 is this line in the controller:

       return $http.get("/api/stuffs/")
         .then(function(response) { // line 15
             return response.data;
       });

How do I mock this correctly?

Edit: I read the documentation mentioned in the comments and and changed up mockBaseService to this:

describe('Controller: AuthCtrl', function() {
    var ctrl, mockBaseService;

    beforeEach(function() {
        // create promise
        var deferred = $q.defer();
        var promise = deferred.promise;
        // make promise.then return {stuffs: "stuffs"}
        promise.then(function(value) { return {stuffs: "stuffs" };  });

        mockBaseService = {
            stuffs: 'stuffs',
            fetch: { 
                stuffs: function() {
                    return promise;
                },
            },
        };

        spyOn(mockBaseService.fetch, 'stuffs');

        module('BaseApp', function($provide) {
            $provide.value('BaseService', mockBaseService);
        });

        module('AuthenticationApp');

        inject(function($controller) {
            ctrl = $controller('AuthCtrl', {
            });
        });
    });

    it('BaseService.fetch.stuffs should be called on page load', inject(function($rootScope) {
        deferred.resolve('test');
        $rootScope.$apply();
        expect(mockBaseService.fetch.stuffs).toHaveBeenCalled();
        expect(ctrl.stuffs).toBe({stuffs: "stuffs"});
    }));

});

But this returns a ReferenceError: $q is not defined error. I tried injecting _$q_ like so:

beforeEach(inject(function(_$q_) {
    $q = _$q_;
    // ... everything else goes here... //

But then I get an error saying Error: Injector already created, can not register a module!. Here: injector already created. can not register a module it says this issue can be solved if we do module('someApp') before inject($someDependency). But I can't do

module('BaseApp', function($provide) {
    $provide.value('BaseService', mockBaseService);
});

before injecting _$q_ since $q is used for mockBaseService. Where do I go from here?

Community
  • 1
  • 1
SilentDev
  • 20,997
  • 28
  • 111
  • 214
  • See [AngularJS $q Service API Reference - Testing Promises](https://docs.angularjs.org/api/ng/service/$q#testing) and [AngularJS $http API Reference - Writing Unit Tests that use $http](https://docs.angularjs.org/api/ng/service/$http#writing-unit-tests-that-use-http). – georgeawg Apr 09 '17 at 21:51
  • @georgeawg Thanks. I read those links and tried implementing what I learned. Can you verify if I'm on the right track, and how to solve the new error I am getting? – SilentDev Apr 10 '17 at 02:03

1 Answers1

1

to mock promises I always do

1) spyOn(service,'method').andCallFake( $q.when( Fixture.data() ) )

Do not create that mockBaseService, I've done that before and its a lot to write for nothing It's also possible to use AngularJS built in way of intercepting $http i.e

2) $httpBackend.when('GET', '/url').respond(Fixture.data())

I definitely prefer 1) as I like to isolate myself in a certain layer when doing testing. It comes down to a matter of taste I guess

Chris Noring
  • 471
  • 3
  • 9
  • Okay thanks. Btw, what exactly is `Fixture` and `Fixture.data()` in example 1? – SilentDev Apr 10 '17 at 01:07
  • Oh thats just my own thing... Its the data you respond with. It good Practice to but all response data in a Fixture like so function Fixture(){ this.user ={ name : 'Adam' }; this.accounts = [ { balance : 100 } ] } – Chris Noring Apr 10 '17 at 09:50
  • Oh okay. So I tried doing what you mentioned (i.e adding the following inside beforeEach: function Fixture() { ... } and then passing Fixture.data() to and.callFake( $q.when( Fixture.data() ) )) but I get an error saying Fixture.data is not a function. Any idea how to solve this? – SilentDev Apr 10 '17 at 19:06
  • here is a jsbin with three options of how to create a fixture, https://jsbin.com/xepaxaw/edit?html,js,console,output – Chris Noring Apr 11 '17 at 09:16
  • I get an error saying `Argument passed to callFake should be a function, got [object Object]` When I do `and.callFake( $q.when( Fixture.data()) )`. I'm using the first option of how to create a fixture, but the second one also gives the same error. Edit: Based on the answer here: http://stackoverflow.com/questions/24997174/jasmine-spies-callthrough-and-callfake maybe it has to do with the fact that `$q.when` becomes a function instead of a value being passed to `and.callFake()`? I'm not sure how to fix this issue. – SilentDev Apr 12 '17 at 21:30
  • you are right, it should be and.returnValue($q.when( data )) or and.callFake( function(){ return { then : function(resolve, reject){ resolve(10) } } } ) @user2719875 – Chris Noring Apr 12 '17 at 22:41