using mocketry to mock subcall

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

using mocketry to mock subcall

Peter Uhnak
Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter

Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Denis Kudriashov
Hi Peter.

You should stub instance instead of class:
     s := Something new.
     s stub askFor...

7 окт. 2017 г. 9:18 пользователь "Peter Uhnák" <[hidden email]> написал:
Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter

Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Peter Uhnak
Thanks Denis, that did the trick.

But I thought that I can also mock at the class level (at least it was shown in the docs).

Peter

On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov <[hidden email]> wrote:
Hi Peter.

You should stub instance instead of class:
     s := Something new.
     s stub askFor...

7 окт. 2017 г. 9:18 пользователь "Peter Uhnák" <[hidden email]> написал:

Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter


Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Denis Kudriashov
Yes, but in that case you stub messages to the class itself. For your example it means:

Something stub askForName willReturn: 'new'.
Something askForName should be: 'new'



2017-10-20 13:58 GMT+02:00 Peter Uhnák <[hidden email]>:
Thanks Denis, that did the trick.

But I thought that I can also mock at the class level (at least it was shown in the docs).

Peter

On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov <[hidden email]> wrote:
Hi Peter.

You should stub instance instead of class:
     s := Something new.
     s stub askFor...

7 окт. 2017 г. 9:18 пользователь "Peter Uhnák" <[hidden email]> написал:

Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter



Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Peter Uhnak
Ah, right. I was expecting that other objects sending the message will receive the mocked result.

Thanks,
Peter

On Fri, Oct 20, 2017 at 2:21 PM, Denis Kudriashov <[hidden email]> wrote:
Yes, but in that case you stub messages to the class itself. For your example it means:

Something stub askForName willReturn: 'new'.
Something askForName should be: 'new'



2017-10-20 13:58 GMT+02:00 Peter Uhnák <[hidden email]>:
Thanks Denis, that did the trick.

But I thought that I can also mock at the class level (at least it was shown in the docs).

Peter

On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov <[hidden email]> wrote:
Hi Peter.

You should stub instance instead of class:
     s := Something new.
     s stub askFor...

7 окт. 2017 г. 9:18 пользователь "Peter Uhnák" <[hidden email]> написал:

Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter




Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Denis Kudriashov
In reply to this post by Denis Kudriashov
So you want to stub message to any instance of class. Right?

Conceptually, in Mocketry/StateSpecs way, it should looks like:

(Instance of: Something) stub askForName willReturn: 'new'.
or:
(Kind of: Something) stub askForName willReturn: 'new'.

But it will not really works. It will work only on instances which are already "stubbed".
Making it really transparent will require crazy magic underhood. And I am not sure that it is really possible. 
Because generally it should cover existing instances and not just newly created during test. 
And for simple case: to automatically stub new instances their classes should be stubbed with special constructors. But system do not know what exact class side messages are constructors.

So now you can just use simple mock which will be returned as simple stub from concrete constructor of your class:

some := Mock new.
some stub askForName: 'new'.
Something stub new willReturn: some.

I think it looks not not bad and no crazy magic is evolved. And of course you can replace mock with real instance if you want:

some := Something new.
some stub askForName: 'new'.
Something stub new willReturn: some.


2017-10-20 14:21 GMT+02:00 Denis Kudriashov <[hidden email]>:
Yes, but in that case you stub messages to the class itself. For your example it means:

Something stub askForName willReturn: 'new'.
Something askForName should be: 'new'



2017-10-20 13:58 GMT+02:00 Peter Uhnák <[hidden email]>:
Thanks Denis, that did the trick.

But I thought that I can also mock at the class level (at least it was shown in the docs).

Peter

On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov <[hidden email]> wrote:
Hi Peter.

You should stub instance instead of class:
     s := Something new.
     s stub askFor...

7 окт. 2017 г. 9:18 пользователь "Peter Uhnák" <[hidden email]> написал:

Hi,

maybe I am missing something fundamental, because this seems like an obvious scenario

I have a class Something with two methods

Something>>askForName
^ UIManager default request: 'Name'

Something>>name
^ self askForName , ' suffix'

Now I want to mock out askForName, so I can run it automatically in tests...

I've tried just stubbing it...

SomethingTest>>testRenameStub
Something stub askForName willReturn: 'new'.
Something new name should be: 'new suffix'

however it still opens the window to ask for the name, so the original method is being called.

Right now I have to stub it with metalinks, which doesn't play well with some tools (hapao)

SomethingTest>>testRenameMeta
| link newName |
link := MetaLink new
metaObject: [ 'new' ];
control: #instead.
(Something >> #askForName) ast link: link.
[ newName := Something new name ]
ensure: [ link uninstall ].
self assert: newName equals: 'new suffix'

Thanks,
Peter




Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Herby Vojčík
Denis Kudriashov wrote:

> So you want to stub message to *any* instance of class. Right?
>
> Conceptually, in Mocketry/StateSpecs way, it should looks like:
>
>     (Instance of: Something) stub askForName willReturn: 'new'.
>
>     or:
>
>     (Kind of: Something) stub askForName willReturn: 'new'.
>
>
> But it will not really works. It will work only on instances which are
> already "stubbed".
> Making it really transparent will require crazy magic underhood. And I
> am not sure that it is really possible.
> Because generally it should cover existing instances and not just newly
> created during test.
> And for simple case: to automatically stub new instances their classes
> should be stubbed with special constructors. But system do not know what
> exact class side messages are constructors.

I had this problem. I tried something like (though not exactly w/ this
code):

Foo stub new will: [ :aMessage |
   | original |
   original := MockExpectedOriginalCall new executeFor: aMessage.
   original stub.
   ^ original ]

but IIRC it failed on doing #stub inside will: block.

>
> So now you can just use simple mock which will be returned as simple
> stub from concrete constructor of your class:
>
>     some := Mock new.
>
>     some stub askForName: 'new'.
>
>     Something stub new willReturn: some.
>
>
> I think it looks not not bad and no crazy magic is evolved. And of
> course you can replace mock with real instance if you want:
>
>     some := Something new.
>
>     some stub askForName: 'new'.
>
>     Something stub new willReturn: some.
>
>
>
> 2017-10-20 14:21 GMT+02:00 Denis Kudriashov <[hidden email]
> <mailto:[hidden email]>>:
>
>     Yes, but in that case you stub messages to the class itself. For
>     your example it means:
>
>         Something stub askForName willReturn: 'new'.
>
>     Something askForName should be: 'new'
>
>
>
>     2017-10-20 13:58 GMT+02:00 Peter Uhnák <[hidden email]
>     <mailto:[hidden email]>>:
>
>         Thanks Denis, that did the trick.
>
>         But I thought that I can also mock at the class level (at least
>         it was shown in the docs).
>
>         Peter
>
>         On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov
>         <[hidden email] <mailto:[hidden email]>> wrote:
>
>             Hi Peter.
>
>             You should stub instance instead of class:
>                   s := Something new.
>                   s stub askFor...
>
>             7 окт. 2017 г. 9:18 пользователь "Peter Uhnák"
>             <[hidden email] <mailto:[hidden email]>> написал:
>
>                 Hi,
>
>                 maybe I am missing something fundamental, because this
>                 seems like an obvious scenario
>
>                 I have a class Something with two methods
>
>                 Something>>askForName
>                 ^ UIManager default request: 'Name'
>
>                 Something>>name
>                 ^ self askForName , ' suffix'
>
>                 Now I want to mock out askForName, so I can run it
>                 automatically in tests...
>
>                 I've tried just stubbing it...
>
>                 SomethingTest>>testRenameStub
>                 Something stub askForName willReturn: 'new'.
>                 Something new name should be: 'new suffix'
>
>                 however it still opens the window to ask for the name,
>                 so the original method is being called.
>
>                 Right now I have to stub it with metalinks, which
>                 doesn't play well with some tools (hapao)
>
>                 SomethingTest>>testRenameMeta
>                 | link newName |
>                 link := MetaLink new
>                 metaObject: [ 'new' ];
>                 control: #instead.
>                 (Something >> #askForName) ast link: link.
>                 [ newName := Something new name ]
>                 ensure: [ link uninstall ].
>                 self assert: newName equals: 'new suffix'
>
>                 Thanks,
>                 Peter
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Denis Kudriashov
Hi Herby.

2017-10-20 18:49 GMT+02:00 Herby Vojčík <[hidden email]>:

I had this problem. I tried something like (though not exactly w/ this code):

Foo stub new will: [ :aMessage |
  | original |
  original := MockExpectedOriginalCall new executeFor: aMessage.
  original stub.
  ^ original ]

but IIRC it failed on doing #stub inside will: block.

So the arguments of block in message #will: are expected to be arguments of stubbing message (not a full message instance).
We can introduce new kind of expected action to automatically stub any result of message send:

Foo stub new willStubRealResult.

With help of new subclass of MockExpectedOriginalMethodCall:

MockExpectedMethodResultStub>>executeFor: anOccurredMessage
realMethodResult := super executeFor: anOccurredMessage.
realMethodResult stub.
^realMethodResult.

MockExpectedMessage>>willStubRealResult
self will: MockExpectedMethodResultStub new

And then you will be able specify expectations for all Foo instances:

(Instance of: Foo) stub someMessage willReturn: #const

And you will be able assert any message which was sent to Foo instances:

(Instance of: Foo) should receive someRequiredMessage which should equal: #expectedValue
 
I committed this code to the dev branch. But I am wondering that such kind of behaviour is really needed. Probably it is useful as you ask about it.



So now you can just use simple mock which will be returned as simple
stub from concrete constructor of your class:

    some := Mock new.

    some stub askForName: 'new'.

    Something stub new willReturn: some.


I think it looks not not bad and no crazy magic is evolved. And of
course you can replace mock with real instance if you want:

    some := Something new.

    some stub askForName: 'new'.

    Something stub new willReturn: some.



2017-10-20 14:21 GMT+02:00 Denis Kudriashov <[hidden email]
<mailto:[hidden email]>>:

    Yes, but in that case you stub messages to the class itself. For
    your example it means:

        Something stub askForName willReturn: 'new'.

    Something askForName should be: 'new'



    2017-10-20 13:58 GMT+02:00 Peter Uhnák <[hidden email]
    <mailto:[hidden email]>>:

        Thanks Denis, that did the trick.

        But I thought that I can also mock at the class level (at least
        it was shown in the docs).

        Peter

        On Sat, Oct 7, 2017 at 11:24 AM, Denis Kudriashov
        <[hidden email] <mailto:[hidden email]>> wrote:

            Hi Peter.

            You should stub instance instead of class:
                  s := Something new.
                  s stub askFor...

            7 окт. 2017 г. 9:18 пользователь "Peter Uhnák"
            <[hidden email] <mailto:[hidden email]>> написал:


                Hi,

                maybe I am missing something fundamental, because this
                seems like an obvious scenario

                I have a class Something with two methods

                Something>>askForName
                ^ UIManager default request: 'Name'

                Something>>name
                ^ self askForName , ' suffix'

                Now I want to mock out askForName, so I can run it
                automatically in tests...

                I've tried just stubbing it...

                SomethingTest>>testRenameStub
                Something stub askForName willReturn: 'new'.
                Something new name should be: 'new suffix'

                however it still opens the window to ask for the name,
                so the original method is being called.

                Right now I have to stub it with metalinks, which
                doesn't play well with some tools (hapao)

                SomethingTest>>testRenameMeta
                | link newName |
                link := MetaLink new
                metaObject: [ 'new' ];
                control: #instead.
                (Something >> #askForName) ast link: link.
                [ newName := Something new name ]
                ensure: [ link uninstall ].
                self assert: newName equals: 'new suffix'

                Thanks,
                Peter







Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Herby Vojčík
Denis Kudriashov wrote:

> Hi Herby.
>
> 2017-10-20 18:49 GMT+02:00 Herby Vojčík <[hidden email]
> <mailto:[hidden email]>>:
>
>
>     I had this problem. I tried something like (though not exactly w/
>     this code):
>
>     Foo stub new will: [ :aMessage |
>        | original |
>        original := MockExpectedOriginalCall new executeFor: aMessage.
>        original stub.
>        ^ original ]
>
>     but IIRC it failed on doing #stub inside will: block.
>
>
> So the arguments of block in message #will: are expected to be arguments
> of stubbing message (not a full message instance).
> We can introduce new kind of expected action to automatically stub any
> result of message send:
>
>     Foo stub new willStubRealResult.

Yeah, exactly, something like that is useful.

> With help of new subclass of MockExpectedOriginalMethodCall:
>
>     MockExpectedMethodResultStub>>executeFor: anOccurredMessage
>
>         realMethodResult := super executeFor: anOccurredMessage.
>
>         realMethodResult stub.
>
>         ^realMethodResult.
>
>     MockExpectedMessage>>willStubRealResult
>
>         self will: MockExpectedMethodResultStub new

Actually I tried this path myself but it failed badly during doing the
sub-#stub with lots of "go one metalevel down" etc. calls in stack, so I
gave up with "I don't understand this to make it work atm".

Does your solution actually work?

> And then you will be able specify expectations for all Foo instances:
>
>     (Instance of: Foo) stub someMessage willReturn: #const

As in, would `Foo new someMessage should be: #const`? In that case,
great and thanks.

> And you will be able assert any message which was sent to Foo instances:
>
>     (Instance of: Foo) should receive someRequiredMessage which should
>     equal: #expectedValue
>
> I committed this code to the dev branch. But I am wondering that such
> kind of behaviour is really needed. Probably it is useful as you ask
> about it.

Herby

Reply | Threaded
Open this post in threaded view
|

Re: using mocketry to mock subcall

Denis Kudriashov
Hi

2017-10-24 20:07 GMT+02:00 Herby Vojčík <[hidden email]>:
Denis Kudriashov wrote:
Hi Herby.

2017-10-20 18:49 GMT+02:00 Herby Vojčík <[hidden email]
<mailto:[hidden email]>>:


    I had this problem. I tried something like (though not exactly w/
    this code):

    Foo stub new will: [ :aMessage |
       | original |
       original := MockExpectedOriginalCall new executeFor: aMessage.
       original stub.
       ^ original ]

    but IIRC it failed on doing #stub inside will: block.


So the arguments of block in message #will: are expected to be arguments
of stubbing message (not a full message instance).
We can introduce new kind of expected action to automatically stub any
result of message send:

    Foo stub new willStubRealResult.

Yeah, exactly, something like that is useful.

With help of new subclass of MockExpectedOriginalMethodCall:

    MockExpectedMethodResultStub>>executeFor: anOccurredMessage

        realMethodResult := super executeFor: anOccurredMessage.

        realMethodResult stub.

        ^realMethodResult.

    MockExpectedMessage>>willStubRealResult

        self will: MockExpectedMethodResultStub new

Actually I tried this path myself but it failed badly during doing the sub-#stub with lots of "go one metalevel down" etc. calls in stack, so I gave up with "I don't understand this to make it work atm".

Probably you not used superclass execution logic where right metalevel logic is implemented (notice that in my example I call "super executeFor:")
 

Does your solution actually work?

And then you will be able specify expectations for all Foo instances:

    (Instance of: Foo) stub someMessage willReturn: #const

As in, would `Foo new someMessage should be: #const`? In that case, great and thanks.

Yes for all questions. And new test is added for this feature
 


And you will be able assert any message which was sent to Foo instances:

    (Instance of: Foo) should receive someRequiredMessage which should
    equal: #expectedValue

I committed this code to the dev branch. But I am wondering that such
kind of behaviour is really needed. Probably it is useful as you ask
about it.

Herby