NAME

Mock::Quick::Presentation - Presentation of Mock::Quick to PDX.pm

DESCRIPTION

This is a presentation for Mock::Quick. This presentation was written for the portland perl mongers for the meeting on 04/13/2011.

QUICK OBJECTS

Quick objects were concieved as an api stand-in
Emulates objects that provide fairly static information
Useful when the information in the object is not relivant to the test

An example function to test:

sub add_and_append {
    my ( $numA, $numB, $subsystem ) = @_;
    my $sum = $numA + $numB;

    return $subsystem->system_name . ":" . $sum;
}
We only want to test the addition, and the appending
We do not really care what subsystem->system_name returns (tested elsware)
Subsystem->new() needs 20 arguments, all are used to by system_name()

Thats where quick objects come in:

is(
    add_and_append( 2, 3, qobj( system_name => 'foo' )),
    "foo:5",
    "add_and_append works"
);

METHOD AUTOVIVIFYING

Sometimes you need to pass in an object that has more methods than you care to mock out, they only need to be present and return something.

Thats where the next feature of Quick Objects comes in. Quick objects will autovivify methods as they are needed. The new methods will be read/write accessors.

my $obj = qobj();
$obj->foo( 'bar' );
is( $obj->foo, 'bar', "foo attribute was set" );
is(
    $obj->foo( 'baz' ),
    'baz',
    "remembers and returns whatever argument you give it."
);

STRICT OBJECTS

Autovivifying can be bad
Autovivification has been reported as a bug in hashes

For cases where you do not want autovivifying you can use strict objects. A strict object will not autovivify.

my $obj = qstrict( foo => 'foo' );
is( $obj->foo, 'foo' );
dies_ok { $obj->bar } "no bar() method defined";

But what if you want to change the specification later?

Easy!

in list context qobj and qstrict return both the new object, and a control object that provides a manipulation interface.

my ( $obj, $control ) = qstrict( foo => 'foo' );
$control->set_attributes( bar => 'bar' );

Why not just provide a manipulation interface as a method on the object?

because it might conflict with the api you are attempting to mock.

CUSTOM METHODS

More often than not you need to mock out methods more complicated than accessors. You can add them to your quick object in 2 ways.

You can't just assign a coderef to an accessor, that would just result in an accessor that returns a coderef. This is important because there are cases where that is what you really want.

The original way to create a custom method is the qmeth() function.

$obj->new_method( qmeth {
    my $self = shift;
    ...
    return 'whatever';
});

This will add a method that will be run next time you call $obj->new_method.

Arguments will not be used to replace the method on the object like they would a in a get/set accessor.

Some people have expressed displeasure with this API. qmeth {...} seems too magical. The alternative is to use the control object.

my ( $obj, $control ) = qobj(...);
$control->set_methods( foo => sub { ... });

CLEARING METHODS/ATTRIBUTES

We have so far seen many ways to add methods, attributes and accessors to a quick object. But you can also clear them! This is particularly useful in strict objects where they will not autovivify back into existance.

One again there are 2 ways to clear an attribute or method. The original way using the qclear method, and the new way using the control object.

# Remove the foo() attribute and any associated value/method
$obj->foo( qclear() );

# Remove a whole list of attributes
$control->clear( qw/foo bar baz/ );

CONTROL OBJECT

The control object provides 2 additional methods.

$control->strict( BOOL )

Lets you toggle strict mode on and off for your object.

$control->metrics

Returns a hashref with all current attributes/methods as keys, the values associated with them are the counts of how many times that attribute has been called. Clearing an attribute will reset its metrics.

MOCKING CLASSES

Sometimes a quick object is not sufficient.

The object you need to mock is instantiated inside the function you're testing
You want to preserve the original class, but you need to override a subset of methods
You want to replace the original class and prevent it from loading
You want to implement a temporary but re-usable class specification

TAKING OVER A CLASS

The class is loaded, you want to change parts of it

Take control of the class
require Some::Package;
my $control = qtakeover( 'Some::Package' );
Have your way with it
$control->override(
    foo => sub { ... }
    bar => sub { ... }
);
Restore the original method
$control->restore( 'foo' );
The original class is completely restored when the control object is destroyed.
$control = undef;

USURPING A CLASS

The class is not loaded, you want to keep it that way, and put something in it's place.

Create the package, %INC is updated for you to prevent the real one from loading.
my $control = qimplement Some::Package => ( ... );

arguments

auto-create new()
-with_new => 1,
subclassing
-subclass => $class || [ @CLASSES ],
quickly generate attributes (read/write)
-attributes => [qw/ foo bar baz /]
simple return value methods
method => 'value'
custom methods
do_it => sub { ... }

The control object returned is identical to that from qtakeover().

ANONYMOUS CLASSES

An anonymous class is the same as userping a class, except instead of using a real package a temporary one is created.

my $control = qclass( ... )

Takes all the same arguments as qimplement.