Spencer Applegate
Spencer Applegate

Reputation: 95

AngularJS + Jasmine how to spy on a service's constructor called in a controller

I am trying to unit test a constructor of mine. Within the constructor, I am injecting a custom service that houses data and makes http requests. within my save function of my constructor, I instantiate an object on the scope by calling the constructor to my custom service.

I want to test to make sure that the constructor of my custom service is being called. I am trying to use a jasmine spy to spy on the constructor call, but unsuccessfully. I am trying to follow an example given by Jasmine on how to perform a spy on the constructor ([Jasmine Spies])1, but it is not working.

My controller is defined as follows:

controller('comments.EditCtrl', ['$scope', '$location', '$routeParams', 'Comment', function($scope, $location, $routeParams, Comment) {

    $scope.save = function(comment) {
        $scope.comment = new Comment(comment);

        $scope.comment.postId = $routeParams.postId;

        Comment.save($scope.comment, function() {
            $location.path('/blog/' + $routeParams.postId);
        });
    };
}])

The custom service is named 'Comment'. The line of interest is

$scope.comment = new Comment(comment);

I can't get the test for this to work properly. My test code for the controller is as follows:

describe('comment.edit', function() {

    beforeEach(function() {
        module('app');
        module('blog.comments');
        module('comments.edit');
    });

    describe('edit controller', function() {
        var $scope, editCtrl, Comment, commentNamespace;

        var mockComment = {
            email: '[email protected]',
            text: 'mock text'
        };

        beforeEach(inject(function($rootScope, $injector, $controller) {
            var routeParamsStub = jasmine.createSpy('routeParamsStub');
            routeParamsStub.postId = '7';

            Comment = $injector.get('Comment');
            commentNamespace = {
                Comment: Comment
            };
            commentNamespace.Comment.save = function(comment, callback) {
                callback();
                return '';
            };

            $scope = $rootScope.$new();

            editCtrl = $controller('comments.EditCtrl', {
                $scope: $scope,
                $routeParams: routeParamsStub,
                Comment: commentNamespace.Comment
            });
        }));

        it('should have a edit controller', function() {
            expect(editCtrl).not.toBe(null);
            expect(editCtrl).not.toBe(undefined);
        });

        describe('save function', function() {

            beforeEach(function() {
                spyOn(commentNamespace, 'Comment');
                $scope.save(mockComment);
            });

            //TODO: figure out how to spy on Comment constructor call
            it('should create a Comment object via its constructor', function() {
                expect(commentNamespace.Comment).toHaveBeenCalled();
            });
        });
    });
});

You can see I am trying create a namespace as Jasmine suggests, and then spy on the 'Comment' for it to hopefully pick up the constructor call. When I run the test, I get the following error message from Karma:

[2013-07-23 21:58:47.720] [DEBUG] config - autoWatch set to false, because of singleRun
INFO [karma]: Karma server started at http://localhost:8080/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 28.0 (Mac)]: Connected on socket id QmSDHIAEJnuGEbe8Zmq3
Chrome 28.0 (Mac) LOG: null
Chrome 28.0 (Mac) comment.edit edit controller save function should create a Comment    object via its constructor FAILED
    Expected spy constructor to have been called.
    Error: Expected spy constructor to have been called.
    at null.<anonymous> (/Users/spencer/Projects/angular-blog/src/app/blog/comments/edit/edit.unit.js:68:55)
Chrome 28.0 (Mac): Executed 20 of 20 (1 FAILED) (0.266 secs / 0.092 secs)
Warning: Task "karma:unit" failed. Use --force to continue.

Aborted due to warnings.

I feel like I have tried every combination of other tactics like spying on Comment.prototype.constructor, but I get the same message every time. Does anyone have any clue on how to do this? I know there is a lot here, let me know if there are any holes in the info where I may have left something out. Thanks

Upvotes: 4

Views: 3918

Answers (1)

Gregg
Gregg

Reputation: 2638

This is due to how jasmine installs spies. When you say spyOn(foo, 'bar') jasmine replaces the bar attribute on foo with a spy object that behaves like a method. This means that when you pass off the commentNamespace.Comment to your controller, it already has a reference to the original constructor, so when you spyOn(commentNamespace, 'Comment') later, it's too late.

Upvotes: 2

Related Questions