Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smalltalk: how to modify self behaviour

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?

like image 266
Zsolt Avatar asked Feb 19 '23 15:02

Zsolt


2 Answers

You could wrap the RealObjectUnderTest's CompiledMethods 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.)

like image 100
Frank Shearar Avatar answered Feb 26 '23 15:02

Frank Shearar


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).

like image 37
Vanessa Freudenberg Avatar answered Feb 26 '23 17:02

Vanessa Freudenberg