I'm porting my favorite Java/JavaScript Mocktito library to Smalltalk. I'm currently at the stage of implementing Spy for stubbing real objects. My problem arises when a spyed object invokes it's own method which is stubbed. Instead of:
self aMethod.
I'd rather want to delegate the call to the spy object:
spyObject aMethod.
Here is a scenario test for the expected behavior:
realObject := RealObjectForTesting new.
spyedObject := Spy new: realObject.
spyedObject when: #accesorWhichReturnsValue thenReturn: 'stubbed value'.
spyedObject accesorWhichCallsSelf.
self assert: (spyedObject verify: #accesorWhichReturnsValue).
Any suggestion?
You could wrap the RealObjectUnderTest
's CompiledMethod
s directly, using the ObjectsAsMethodsWrapper library. This provides a convenient API to install and remove the wrappers, together with some convenient predefined wrappers.
These would intercept self-sends because the wrappers are installed in the real object's method dictionary, and thus may perform arbitrary changes to the message before passing it on to the underlying CompiledMethod
.
While my example shows how to memoize a method call without touching source code, it should provide you with the basic knowledge required to mock out method calls.
This particular technique does have a limitation: it intercepts self sends to messages that the class itself defines. So if Foo
subclasses Bar
and you install wrappers on Foo
, you won't intercept messages that form part of Bar
's protocol (unless of course you wrap those too).
You won't be able to intercept ifTrue:ifFalse:
, timesRepeat
or similar messages in a Squeak or Pharo image (and likely in GNU Smalltalk too), because those are not message sends: compile-time transformations inline these message sends into jump bytecodes. (The illusion of a message send is relatively convincing because the Decompiler
knows how to untransform the bytecodes back into ifTrue:ifFalse:
or whatever.)
You make your "spy" be a "wrapper" object that only implements doesNotUnderstand:
and then swap it for the real object using become:
.
The spy's doesNotUnderstand:
method will be called for all messages, and you can then e.g. log its argument (which is a Message object) and send it to the original object.
If you browse implementers of doesNotUnderstand:
in your Smalltalk image you may find a couple of examples (e.g. in Squeak there is MessageCatcher
and ObjectViewer
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With