unit-testingmockingjasminespyon

spyOn and createSpyObj in the same spec jasmine


I'm using Jasmine to test this

class X{
   test(obj){
    return obj.someFunction()+100
  }
}

so my spec is

it("test if someFunction is called and return value calculated correctly",()=>{
  let x = new X()

   let mockObj= jasmine.createSpyObj(["someFunction"])
      mockObj.someFunction.and.callFake(()=> 2000)

  let returnValue= x.test(mockObj)

   expect (mockObj.someFunction).toHaveBeenCalled()
   expect (returnValue).toBe(2000+100)
})

And this is ok Now I want to check if the parameter of test() is an object as data type so I want to do this

   spyOn(x,"test")
   let mockObj= jasmine.createSpyObj(["someFunction"])
   x.test(mockObj)
   expect(x.test.calls.allArgs()[0][0]).toBeInstanceOf(Object)

And this is ok in another it()

But why can't we do both in the same it() block? Is it not valid to use spyOn() and createSpyObj() in the same it() block?

If I did so the it() will fail! and this is the error I'm getting

Expected spy unknown.someFunction to have been called
Expected undefined to be 2100

Solution

  • When we do spyOn, we are attaching a spy on the method and letting go of its implementation details. So after we spy on test method, we can see how many times it was called, how it was called, etc. but we lose it's implementation details (what's inside of the method).

    To get the best of both worlds (still have implementation details and know if it was called, how it was called, etc.), we need to use .and.callThrough();.

    Change the first line here like so:

    // add .and.callThrough() here.
    spyOn(x,"test").and.callThrough();
    let mockObj= jasmine.createSpyObj(["someFunction"])
    x.test(mockObj)
    expect(x.test.calls.allArgs()[0][0]).toBeInstanceOf(Object)
    

    That way, the test method will be spied upon and the actual method will be called.

    Edit You can use callFake as well but you have to provide a function implementation.

    spyOn(x,"test").and.callFake((arg) => {
      if (arg) {
        return 300;
      }
      return 400;
    });
    

    You can do something based on the argument. In this case, if it exists, it will return 300 and if it doesn't, it will return 400;