2

I'm brand new to testing, and I've been trying to find the best strategy for unit testing an AngularJS controller with a service dependency. Here's the source code:

app.service("StringService", function() {
    this.addExcitement = function (str) {
        return str + "!!!";
    };
});

app.controller("TestStrategyController", ["$scope", "StringService", function ($scope, StringService) {
    $scope.addExcitement = function (str) {
        $scope.excitingString = StringService.addExcitement(str);
    };
}]);

And the test I'm using currently:

describe("Test Strategy Controller Suite", function () {
    beforeEach(module("ControllerTest"));

    var $scope, MockStringService;

    beforeEach(inject(function ($rootScope, $controller) {
        $scope = $rootScope.$new();
        MockStringService = jasmine.createSpyObj("StringService", ["addExcitement"]);
        $controller("TestStrategyController", {$scope: $scope, StringService: MockStringService});
    }));

    it("should call the StringService.addExcitement method", function () {
        var boringString = "Sup";
        $scope.addExcitement(boringString);
        expect(MockStringService.addExcitement).toHaveBeenCalled();
    });
});

This test passes, but I'm confused about something: if I change the name of the method in the service (let's say I call it addExclamations instead of addExcitement but not where it is used in the controller (still says $scope.excitingString = StringService.addExcitement(str);), my tests still pass even though my controller is now broken. However, once I change the method name in the controller as well, so as to fix the actual breakage caused by changing the service's method name, my tests break because it's trying to call the old addExcitement method.

This would indicate that I would need to manually keep the method names in sync with the service by changing the jasmine spy object line to MockStringService = jasmine.createSpyObj("StringService", ["addExclamations"]);.

All of this seems backwards to me, since I feel like my test should break when I change the service's method name without changing how the controller references that service name. But I'm not sure how to get the best of both worlds here, because if I'm expecting my test to keep track of that service name somehow, there's no way for it to pass again when I change the method name in both the service and the controller because the spyObj still has the old name.

Any insight or advice about the strategy behind this would be greatly appreciated. I'm going to be teaching this to some students, and am mostly trying to make sure I'm following best practices with this.

1 Answer 1

1

I'd say that's the expected result of the way your test code works, simply because you created a "brand new" mock service object. I guess you know what I am talking about.

What I usually do is get the service instance and mock the method, instead of creating a completely new mock object.

   beforeEach(inject(function ($rootScope, $controller, $injector) {
       $scope = $rootScope.$new();
       MockStringService = $injector.get('StringService'); 
       spyOn(MockStringService , 'addExcitement').andReturn('test');
       $controller("TestStrategyController", {$scope: $scope, StringService: MockStringService});
   }));

please note that andReturn() is a jasmine 1.x method, depends on the version you are using, you may want to change the code a little bit.

Having it this way, if you change the method name in StringService, you should get errors from spyOn() method, as the method doesn't not exist any more.

Another thing is that, you don't have to use $injector as I did to get the service instance, you can just inject your service instead in fact. I don't recall why I did it this way. :)

Sign up to request clarification or add additional context in comments.

2 Comments

I keep going back and forth in my opinion about mocking the service completely (using something like a jasmine.createSpyObj()) and bringing in a reference to the actual service and making a spy out of it. This seems to be another point in favor of the latter, because that worked perfectly. It's still annoying to have to change the test code if I make a change like that, but I guess that's just the nature of having up-to-date/accurate tests. Thanks!
if you make a change like that, you still have to change the test code, at least for this line expect(MockStringService.addExcitement), don't you? I am thinking you can iterate through all functions of the actual service, spy on them and then return the spy object reference so you can setup expectation later. With this way, you can achieve what you want, but you may end up testing against wrong method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.