Long story short, I spied on a function, I saw the debug log coming from the function, which means it definitely has been called, but the test result kept saying that it was never called.
After doing some experiments, I figured out the reason which was haunting my test expectation, is that jasmine's spyOn() will replace the spied function's reference with a created intermediate function.
This can be illustrated as below.
Initially you have Object B with a member method A, which is bound to Event C as its event handler.
I'm using AngularJS, so in my case the code looks like this.
$scope.$on('event:c', objectB.methodA);
Now I want to test if method A is ever being called if event C is triggered, I'll spy on method A, trigger the event C, and expect method A to have been called.
spyOn(objectB, 'methodA');
// Trigger event C
expect(objectB.methodA).toHaveBeenCalled();
What happened after spyOn() should be like below picture.
Any external call to method A will first invoke the intermediate function(Spy), and then delegated to real Method A
NOTE: Actually you'll have to specify spyOn().and.callThrough(), otherwise the function call won't be delegated to real Method A. Read it in Jasmine Doc
Then you think your test will pass this easy one.
Wrong, you got a error result indicating that method A was never called.
A console-lover like me would want to put a console.log("Fxxk!!") in Method A and see if the message is there, and shit it is there.
This gave me pain-in-ass until I figured out what spyOn() does behind the scene and why it kept saying method A was never called.
Because, in fact, it's just never called.
What is bound to Event C is still the reference to the original method A.
When Event C is triggered, the call ignores method A(Spy) and directly goes to real method A, that's why we can see "Fxxk" in log message but failed to expect spy function being called.
Workarounds
- Bind an anonymous function to Event C, call method A in the anonymous function
Literally calling method A in code will go to its spied version, thus Jasmine spy can catch the call.$scope.$on('event C', function(){ objectB.methodA(); });
- Don't spy at all. Instead of testing whether method A has been called, test execution result of method A
// In code function methodA(){ objectB.method_A_called = true; } // In unit test expect(objectB.methodA_called).toBe(true);
No comments:
Post a Comment