spyOn().and.returnValue() calls the actual implementation

16,490 views
Skip to first unread message

Fredrik Boström

unread,
Feb 27, 2014, 3:31:40 PM2/27/14
to jasmi...@googlegroups.com
Hello. 

I'm getting acquainted with Jasmine and became a bit baffled by the way the spies work. 

I'm testing a controller and I've created a spy on a service that it uses. 

      beforeEach(module(function ($provide) {
        $provide.service('myService', MyService);
      }));

And in one of the beforeEach blocks, I create the spies on each of the service's methods.

      beforeEach(inject(function (_$injector_, _$controller_, _$rootScope_) {
        spyOn(myService, 'getAll').and.returnValue([{id: "2", type: "0", name: "Foo"}]);
        spyOn(myService, 'getOne').and.returnValue({id: "3", type: "1", name: "Bar"});
        spyOn(myService, 'save').and.returnValue(true);
      }));

And in the tests I use the spy to investigate the calls to it.

     it("should call service.getOne with the id from the routeParams", function () {
        routeParams = {id: "2"}
        createController();
        expect(service.getOne).toHaveBeenCalledWith("2");
      });

This all works perfectly fine. So what's the problem? 

Well, I assumed that since we're using a spy and not the .and.callThrough(), the actual implementation would not get called. However, when I inserted a console.log() into the actual service implementation, the log message showed up in the terminal when running the tests with karma-jasmine. This indicates that the actual implementation in fact is called although we're using a spy. 

According to the docs (http://jasmine.github.io/2.0/introduction.html), this should also be the case (specifically the test it("should not effect other functions" in the code for and.returnValue). 

What I don't understand is why this is the case. Why would we want pass the call on to the actual implementation if we're explicitly creating a mock to hide it. And why is there an .and.callThrough if all calls are passed through anyway? What if the service uses $http or accesses a database? Am I not using the spy to prevent the actual implementation from being called?

Am I missing something?

So the only way not to have my tests call the actual implementation is to create a separate mock object with identically named mock functions, inject that into my model in place of the real service and put spies on that object instead. 


Gregg Van Hove

unread,
Feb 27, 2014, 5:16:32 PM2/27/14
to jasmi...@googlegroups.com
Once you spy on a function, attempting to invoke it should never call the real implementation unless you use `and.callThrough()`. A very simplified example:

it("should not callThrough unless told", function() {
  var foo = {
    bar: function() {
      console.log("bar called");
    }
  };
  spyOn(foo, 'bar').and.returnValue(321);
  foo.bar('hi');

  expect(foo.bar).toHaveBeenCalledWith('hi');
  // there should be no console log
});

In addition, with the angular injector stuff, make sure that the `myService` that you're spying on is in fact the `service` you're using in your tests.

-Gregg


-Gregg


--
You received this message because you are subscribed to the Google Groups "Jasmine" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jasmine-js+...@googlegroups.com.
To post to this group, send email to jasmi...@googlegroups.com.
Visit this group at http://groups.google.com/group/jasmine-js.
For more options, visit https://groups.google.com/groups/opt_out.

Fredrik Boström

unread,
Mar 3, 2014, 8:47:22 AM3/3/14
to jasmi...@googlegroups.com
Thank you for your reply. Yes you're absolutely right and I'm a fool for working too late and not noticing my own mistakes. Turned out I was missing something. I had another test using the service and thus triggering the console.log. I apologise for wasting your time. 

 - fredrik
Reply all
Reply to author
Forward
0 new messages