Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Test::MockObject doesn't fool UNIVERSAL

by water (Deacon)
on Jul 10, 2005 at 21:56 UTC ( [id://473797]=perlquestion: print w/replies, xml ) Need Help??

water has asked for the wisdom of the Perl Monks concerning the following question:

Perhaps I'm wanting too much, but I think I'm seeing that an object mocked by Test::MockObject doesn't fool UNIVERSAL::isa and UNIVERSAL::can. Here's some code giving the idea:
use strict; use warnings FATAL => 'all'; use Test::MockObject; use Test::More tests => 6; my $c = Test::MockObject->new; $c->set_isa('Foo'); $c->mock('id', sub {return -1;}); ok($c->can('id'), "c can id (via 'can' method)"); ok($c->isa('Foo'), "c isa Foo (via 'isa' method)"); isa_ok($c, 'Foo', 'c isa_ok Foo'); is($c->id, -1, 'mocked accessor'); # FAILS! ok(UNIVERSAL::isa($c, 'Foo'), 'c isa Foo (via UNVERSAL::isa)'); # FAILS! ok(UNIVERSAL::can($c, 'id'), 'c can id (via UNIVERSAL::can');
Is this because UNIVERSAL is the root of the object hierarchy, and thus can't be fooled?

If so, it would seem that methods which need to check the type of their arguments (asserting if the wrong param type sent, for example), should use  $self->isa rather than UNIVERSAL::isa to do so, as otherwise you can't use Mock Objects to test the methods.

(In contrast, this nice perl OO style guide suggests UNIVERSAL:: over the method call approach. Which I think isn't great advice if you'll be testing with Test::MockObject.)

This isn't a SOPW post per se, but I end with the following question: are the observations above correct, or did I misuse  UNIVERSAL?

Thanks!

water

Retitled by davido from 'Class::MockObject doesn't fool UNIVERSAL'.

Replies are listed 'Best First'.
Re: Test::MockObject doesn't fool UNIVERSAL
by itub (Priest) on Jul 10, 2005 at 22:25 UTC
    There are two modules on CPAN, curiously called UNIVERSAL::isa and UNIVERSAL::can which fix this problem. Read the documentation for a detailed discussion about this issue, including the problems with Test::MockObject.
Re: Test::MockObject doesn't fool UNIVERSAL
by ysth (Canon) on Jul 11, 2005 at 01:01 UTC
    Yes, the default UNIVERSAL:: methods don't do what the overriding methods do.

    The guide you refer to prefers UNIVERSAL::isa (etc.) over $obj->isa because $foo->method croaks when $foo is a reference that isn't an object. This has the cost of breaking anything that overrides the UNIVERSAL method.

    I've started preferring always using the method call, wrapped in eval:

    if (eval { $obj->isa("Foo") }) { print "It's a foo!"; }
    The only drawback that I see with this so far is that $@ is altered and would need to be localized if it's being otherwise used, such as in a DESTROY routine.
Re: Test::MockObject doesn't fool UNIVERSAL
by chromatic (Archbishop) on Jul 11, 2005 at 01:04 UTC

    Test::MockObject provides isa() and can() methods that do the right things. If you don't call them, though, you won't get the right answers.

    Calling a base class method directly on a derived class that may have overrdden the method is a bad idea. Any code that does that needs a very good reason to do so, or it's broken. (The only good reason I can think of is in UNIVERSAL::isa, which I'll probably use in the next version of Test::MockObject.)

    I have the temptation to create a module called UNIVERSAL::new and tell everyone to create their objects with UNIVERSAL::new( 'ClassName', %arguments );.

Re: Test::MockObject doesn't fool UNIVERSAL
by etcshadow (Priest) on Jul 11, 2005 at 01:30 UTC
    Well, the problem is that there is a difference between:
    $x->method(...)
    and
    class::method($x, ...)
    even if "class" is the class in which $x is blessed, or one of the ancestors of that class. When you say $x->method(...), you are invoking what's called "method dispatch", a (breadth-first) search for a method in the class in which the object is blessed and its ancestors. When you say class::method($x, ...) (basically equivalent to $x->class::method(...)), you are foregoing the search and telling perl which class's method to use.

    Now, in this example, you are going straight to UNIVERSAL for its can and isa methods, rather than allowing method dispatch to find the can and isa methods in the (nearer than UNIVERSAL) ancestors of your class (or the class itself). Thus, what you're getting is UNIVERSAL::can, rather than Class::MockObject::can (which is what the method dispatch would have found).

    The last thing, though, is that perl almost forces you to do this. If you've got an object, and you don't know what it is, then calling $x->can('foo') could be a really bad idea. Well, that's not true... if you know that $x is, in fact, an "object" then you're cool. The problem is: what if you don't know whether $x is an object (a blessed reference), or if it's just a plain, unblessed reference, or not even a reference at all. Then, if it *can* foo, then it'll return true, but if it *cannot* foo, then it might die! Anyway, the UNIVERSAL::can and UNIVERSAL::isa CPAN modules mentioned by itub are pretty cool and basically deal with this, in a way (but they warn if you do it).

    ------------ :Wq Not an editor command: Wq

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://473797]
Approved by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (5)
As of 2024-04-25 10:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found