Mike Chamberlain
Mike Chamberlain

Reputation: 42440

Jasmin spyOn only works when spied-on function is wrapped, but not when delegated to

In this contrived case, I have a service that takes one dependency. The service exposes one method that needs to call through to the dependency.

If I wrap this method call in its own function, then spyOn works as expected (MyService1). However, if I delegate to this service directly, then spyOn fails (MyService2).

My questions are:

  1. Can someone please explain this behaviour?
  2. If I want to use spyOn, must I re-write all my directly delegated methods as wrappers, or is there a way around this?

Thanks.

describe('spyOn problem', function() {

    // wraps dependency function - can spy on
    var MyService1 = function (dependency) {
        this.continue = function() {
            dependency.nextStage();
        };
    };

    // delegates to dependecy function - cannot spy on
    var MyService2 = function (dependency) {
        this.continue = dependency.nextStage;
    };

    var dependencyMock = {
        nextStage: function () {
        }
    };

    it('should call nextStage method of MyService dependency', function () {
        var service = new MyService1(dependencyMock);
        spyOn(dependencyMock, 'nextStage'); // also tried with .andCallThrough()
        service.continue();
        expect(dependencyMock.nextStage).toHaveBeenCalled();
        // Fail: "Expected spy nextStage to have been called"
    });

    it('should call nextStage method of MyService2 dependency', function () {
        var service = new MyService2(dependencyMock);
        spyOn(dependencyMock, 'nextStage');
        service.continue();
        expect(dependencyMock.nextStage).toHaveBeenCalled();
        // Pass
    });
});

Upvotes: 0

Views: 578

Answers (1)

user3037143
user3037143

Reputation: 926

Jasmine Spies works on the objects, which means for spyOn(dependencyMock, 'nextStage'), the function nextStage on the object dependencyMock will be replaced with a jasmine spy function with this statement.

In MyService2 test case, the spy is installed after actually assigning the function nextStage to continue, which means continue will refer to the actual function nextStage and not to spy. By modifying the test case as below, the spy will be assigned instead.

it('should call nextStage method of MyService2 dependency', function () {
    spyOn(dependencyMock, 'nextStage');
    var service = new MyService2(dependencyMock);
    service.continue();
    expect(dependencyMock.nextStage).toHaveBeenCalled();
    // Pass
});

In the MyService1 test case, even though you are calling service.continue(), it internally operates on 'dependencyMask` object for which spy is installed.

Upvotes: 2

Related Questions