-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Delegate hash access on spy to the real object #22
base: master
Are you sure you want to change the base?
Conversation
Converts the spy to a tied hash so that direct hash operations are delegated to the real object. Adds restriction that spy() can only accept a blessed HASH reference.
1 similar comment
Hi Bob,
I've been taking some time to think about this change. I'm not keen on the
level of intimacy between the spy and the object it is spying on created by
this PR. My original intention was for the spy to operate only on the
object methods so that it doesn't touch its internals. Currently, I'm more
inclined to revert the original PR (I have already done this temporarily).
Perhaps you could explain more about your use case and why you would need
it. I believe the use of spies should focus on the public interactions
between the client and the object. I assume you are testing the client, and
the spy is used to verify how the client interacts with a dependent object.
How the dependent object works internally (with methods called indirectly)
should not be important. In that situation, I would just stub the direct
method and ignore the indirect one since I'm testing the client and I don't
care how the dependent object works.
Cheers,
Steven
…On Tue, 15 Jan 2019 at 04:09, Bob Showalter ***@***.***> wrote:
The commit at 2bd4439
<2bd4439>
to pass the spy to method calls works great for method dispatch, but breaks
direct access to the blessed object (hash reference).
For example, the following fails under 0.65:
{
package MyClass;
sub new { bless { foo => 'FOO' }, shift }
sub say_foo { my $self = shift; print $self->{foo}, "\n" }
}
use Test::Mocha;my $obj = MyClass->new;my $spy = spy($obj);$spy->say_foo; # should print "FOO"
Instead of printing 'FOO', the last line prints nothing (undef), because
in sub say_foo, $self->{foo} is accessing the foo element of the spy
object instead of the real object.
This PR converts the spy to a tied hash (using the core Tie::ExtraHash
module) so that direct hash operations are delegated to the real object.
The spy's attributes can be access through tied($spy)->[1]. This
technique is applied in the __object, __calls, and __stubs methods.
As a limitation, spy() only accepts blessed HASH references. That
probably covers the vast majority of use cases. Support for blessed SCALAR
and ARRAY references can be added fairly easily.
------------------------------
You can view, comment on, or merge this pull request online at:
#22
Commit Summary
- Delegate hash access on spy to the real object
File Changes
- *M* dist.ini
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-0> (1)
- *M* lib/Test/Mocha.pm
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-1> (2)
- *M* lib/Test/Mocha/Spy.pm
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-2> (15)
- *M* lib/Test/Mocha/SpyBase.pm
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-3> (6)
- *A* lib/Test/Mocha/SpyHash.pm
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-4> (19)
- *M* t/spy.t
<https://github.com/stevenl/Test-Mocha/pull/22/files#diff-5> (30)
Patch Links:
- https://github.com/stevenl/Test-Mocha/pull/22.patch
- https://github.com/stevenl/Test-Mocha/pull/22.diff
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#22>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABJmWXyUgrhukMw60RiXmeeYwGvblUkRks5vDUZ1gaJpZM4aAKAR>
.
|
Hi Steven, Here's a silly example to illustrate what I'm trying to do. Consider the following "temperature conversion" class: package TempConverter;
sub new
{
my $class = shift;
bless {
factor => 1.8,
offset => 32,
}, $class;
}
sub temp_from_service
{
my $self = shift;
# ... imagine a process that consults an external web service
print "(external service called)\n";
10;
}
sub converted_temp
{
my $self = shift;
my $temp = $self->temp_from_service;
$temp * $self->{factor} + $self->{offset};
} Now I want to write a unit test for the use Test::Mocha;
my $subject = TempConverter->new(foo => 'CUSTOM');
my $spy = spy($subject);
stub { $spy->temp_from_service } returns(5);
$spy->converted_temp; # <-- expect result 41 here Under Test::Mocha 0.64, the stub is ignored and the actual Under Test::Mocha 0.65, the stub is called and Under this PR, the test works properly and the the expected output of 41 is produced. The accesses to I'm used to Ruby test libraries like An alternate way to approach this test would be to use something like I hope that makes sense. Thanks for your consideration. |
What you're doing sounds reasonable. I didn't particularly have that use
case in mind when I built this. I'm going to see if there is another
approach I can take, which might involve some overhauling. But in the
meantime, I'll put in your changes in and release it as a development
release.
…On Tue, 5 Feb 2019 at 17:40, Bob Showalter ***@***.***> wrote:
Hi Steven,
Here's a silly example to illustrate what I'm trying to do. Consider the
following "temperature conversion" class:
package TempConverter;
sub new
{
my $class = shift;
bless {
factor => 1.8,
offset => 32,
}, $class;
}
sub temp_from_service
{
my $self = shift;
# ... imagine a process that consults an external web service
print "(external service called)\n";
10;
}
sub converted_temp
{
my $self = shift;
my $temp = $self->temp_from_service;
$temp * $self->factor + $self->offset;
}
Now I want to write a unit test for the converted_temp method. I'd like
to stub the temp_from_service method so that my test is not dependent on
external services. So I'd like to something like this:
use Test::Mocha;my $subject = TempConverter->new(foo => 'CUSTOM');my $spy = spy($subject);
stub { $spy->temp_from_service } returns(5);$spy->converted_temp; # <-- expect result 41 here
Under Test::Mocha 0.64, the stub is ignored and the actual
temp_from_service is called (because $self is the actual object and not
the spy). The output is 50 (based on the the temp_from_service return).
Under Test::Mocha 0.65, the stub is called and $temp is set to 5.
However, the following line doesn't work properly, because factor and
offset are accessing the blessed spy hash and not the original object.
The output is 0.
Under this PR, the test works properly and the the expected output of 41
is produced. The accesses to factor and offset are delegated to the
original object.
I'm used to Ruby test libraries like mocha and rspec-mocks, where this
kind of stubbing works fine. Perl complicates things because accesses to
data on the blessed object does not go through normal method dispatch but
instead requires the tie mechanism.
An alternate way to approach this test would be to use something like
Sub::Override to stub TempConverter::temp_from_service. At that point, i
don't need the Test::Mocha spy at all. But I'm keen to use Test::Mocha
throughout, as it feels much more like a "modern" mocking/stubbing system.
I hope that makes sense. Thanks for your consideration.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#22 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABJmWcJ3cZJteFKtz_aMpz1JsyqQjmGQks5vKbQTgaJpZM4aAKAR>
.
|
Hi Steven, I just added a couple of minor fixes to this PR. I wonder if you'd had a chance to think about this direction any more? This pattern is extremely useful to me, but perhaps I'm using an unconventional (or even ill-conceived) testing strategy? If you're not interested in extending the current use Test::Mocha;
my $obj = MyClass->new;
my $spy = spy($obj); # create an 0.65-style spy
my $ext_spy = ext_spy($obj); # create an extended spy that delegates Or, failing that, would you be open to a few tweaks to the internals that would allow this extended spy to be implemented in a subclass? use Test::Mocha::ExtSpy; # extends behavior of spy
my $obj = MyClass->new;
my $spy = spy($obj); # create an extended spy that delegates Just some ideas... |
Hi Bob,
Thanks.
I've been really busy lately so I havent done what i was planning to do. I
hope to release something soon.
Cheers,
Steven
…On Wed, 24 Apr 2019, 16:39 Bob Showalter, ***@***.***> wrote:
Hi Steven,
I just added a couple of minor fixes to this PR.
I wonder if you'd had a chance to think about this direction any more?
This pattern is extremely useful to me, but perhaps I'm using an
unconventional (or even ill-conceived) testing strategy?
If you're not interested in extending the current spy like this, would
you be open to adding a separate kind of spy that would have this extended
behavior?
use Test::Mocha;my $obj = MyClass->new;my $spy = spy($obj); # create an 0.65-style spymy $ext_spy = ext_spy($obj); # create an extended spy that delegates
Or, failing that, would you be open to a few tweaks to the internals that
would allow this extended spy to be implemented in a subclass?
use Test::Mocha::ExtSpy; # extends behavior of spymy $obj = MyClass->new;my $spy = spy($obj); # create an extended spy that delegates
Just some ideas...
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#22 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAJGMWLUDDXFMGRZSFXGFNDPSBWI7ANCNFSM4GQAUAIQ>
.
|
The commit at 2bd4439 to pass the spy to method calls works great for method dispatch, but breaks direct access to the blessed object (hash reference).
For example, the following fails under 0.65:
Instead of printing 'FOO', the last line prints nothing (undef), because in
sub say_foo
,$self->{foo}
is accessing thefoo
element of the spy object instead of the real object.This PR converts the spy to a tied hash (using the core
Tie::ExtraHash
module) so that direct hash operations are delegated to the real object.The spy's attributes can be access through
tied($spy)->[1]
. This technique is applied in the__object
,__calls
, and__stubs
methods.As a limitation,
spy()
only accepts blessed HASH references. That probably covers the vast majority of use cases. Support for blessed SCALAR and ARRAY references can be added fairly easily.