NAME
    Test::Mock::Object - Dead-simple mocking

VERSION
    version 0.1

SYNOPSIS
      use Test::Mock::Object qw(create_mock read_only);
  
      my $r = create_mock(
          package => 'Apache2::RequestRec',
          methods => {
              uri        => read_only('/foo/bar'),
              status     => undef,                   # read/write method
              headers_in => {},
              param      => sub {
                  my ( $self, $param ) = @_;
                  my %value_for = (
                      skip_this => 1,
                      thing_id  => 1001,
                  );
                  return $value_for{$param};
              },
          },
          method_chains => [                         # arrayref
              [ qw/foo bar baz/ => $final_value ],    # of arrayrefs
          ],
      );

DESCRIPTION
    Mock objects can be a controversial topic, but sometimes they're very
    useful. However, mock objects in Perl often come in two flavors:

    *   Incomplete mocks of existing modules

    *   Generic mocks with clumsy interfaces I can never remember

    This module is my attempt to make things dead-easy. Here's a simple mock
    object:

        my $mock = create_mock(
            package => 'Toy::Soldier',
            methods => {
                name   => 'Ovid',
                rank   => 'Private',
                serial => '123-456-789',
            }
        );

    You can figure out what that does and it's easy. However, we have a lot
    more.

    Note, that while "$mocked->isa($package)" will return true for the name
    of the package you're mocking, but the package will be blessed into a
    namespace similar to "MockMeAmadeus::$compact_package", where
    $compact_package is the name of the blessed package, but with "::"
    replaced with underscores, along with a prepended "_V$mock_number".
    Thus, mocking something into the "Foo::Bar" package would cause "ref" to
    return something like "MockMeAmadeus::Foo_Bar_b1".

    If you need something more interesting for "isa", pass in your own:

        my $mock = create_mock(
            package => 'Toy::Soldier',
            methods => {
                name   => 'Ovid',
                rank   => 'Private',
                serial => '123-456-789',
                isa    => sub {
                    my ( $self, $class ) = @_;
                    return $class eq 'Toy::Soldier' || $class eq 'Toy';
                },
            }
        );

FUNCTIONS
    These functions are exportable individually or with "::all":

      use Test::Mock::Object qw(
        add_method
        create_mock
        read_only
        reset_mocked_calls
      );
      # same as
      use Test::Mock::Object ':all';

  "create_mock( package => $package, methods => \%methods )"
      use Test::Mock::Object qw(create_mock read_only);

      my $r = create_mock(
          package => 'Apache2::RequestRec',
          methods => {
              uri        => read_only('/foo/bar'),
              status     => undef,                   # read/write method
              headers_in => {},
              param      => sub {
                  my ( $self, $param ) = @_;
                  my %value_for = (
                      skip_this => 1,
                      thing_id  => 1001,
                  );
                  return $value_for{$param};
              },
          }
          method_chains => [                          # arrayref
              [ qw/foo bar baz/ => $final_value ],    # of arrayrefs
          ],
      );
      say $r->uri;                 # /foo/bar
      say $r->param('thing_id');   # 1001
      say $r->status;              # undef
      $r->status(404);
      say $r->status;              # 404
      $r->uri($new_value);         # fatal error
      say $r->foo->bar->baz;       # $final_value (from method_chains)

    We simply declare the package and the methods we need. If the package
    has not yet been loaded, we alter %INC to ensure the package cannot be
    loaded after this. This is a convenience if we have a module that's very
    hard to load.

    As for the methods, if we point to a coderef, that's the method. If we
    point to *anything else*, the method will return that value and you can
    set it to a new value.

    Arguments to "create_mock()" are:

    *   "package"

        The name of the package we will mock.

        Required.

    *   "methods"

        Key/Value pairs of methods. Keys are method names of the objects and
        values what those methods return, with one important exception.

        If the value is a subroutine reference, that reference *becomes* the
        method for the key. If you want a method to *return* a subroutine
        reference, you need to wrap that in another subroutine reference.

            method => sub { sub { ... } }

        Optional.

    *   "method_chains"

        (Still experimental)

        An array reference of array references. Optional.

            my $mock = create_mock(
                package => 'Some::Package',
                method_chains => [
                    [ qw/ name to_string Ovid / ],
                    [ qw/ name reversed divO / ],
                    [ qw/ foo bar baz 42 / ],
                ]
            );
            say $mock->name->to_string;     # Ovid
            say $mock->name->reversed;      # divO
            say $mock->foo->bar->baz;       # 42

        We have incomplete support for chains that might start with the same
        method.

  "read_only($value)"
    When used with a method value, will throw a fatal error if you try to
    set that value:

        uri => read_only('/foo/bar')

  "add_method($mock_object, $method_name, $value)"
    Just like the key/value pairs to "create_mock()", this adds a
    getter/setter for $value. If $value is a code reference, it will be
    added directly as the method. You can make the value read-only, if
    needed:

        add_method($mock_object, 'created', read_only(DateTime->now));

  "reset_mocked_calls($mock_object)"
        reset_mocked_calls($mock_object);

    This reset the "times called" internals to 0 for every method. See
    "Inside the object".

Mocked Methods
  "isa"
        if ( $r->isa('Apache2::RequestRec') ) {
            ...
        }

    Returns true if the classname passed in matches the name passed to
    "create_mock"

Inside the object
    The object returned encapsulates all data thoroughly. However, it's a
    blessed hashref whos keys are the names of the methods, each pointing to
    hashref with information about how they were called in the code. So our
    example above would have this:

        bless(
            {
                'foo' => {
                    'times_called'       => 1,
                    'times_with_args'    => 0,
                    'times_without_args' => 1
                },
                'headers_in' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                },
                'isa' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                },
                'param' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                },
                'some_object' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                },
                'status' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                },
                'uri' => {
                    'times_called'       => 0,
                    'times_with_args'    => 0,
                    'times_without_args' => 0
                }
            },
            'MockMeAmadeus::Apache2_RequestRec_V1'
          )

    "times_called" is the number of times that method was called in the code
    you're using it in.

    "times_with_args" is the number of times that method was called with
    arguments.

    "times_without_args" is the number of times that method was called
    without arguments.

Unknown methods
    The methods you request are the methods you will receive. Any attempt to
    call unknown methods will be a fatal error.

BEST PRACTICES
  Don't Use Mock Objects
    See "Interface Changes". However, if you're relying on sometthing you
    don't control, such as an object that requires a database connection or
    an internet connection, a mock might be acceptable.

  Only Mock the Methods You Use
    You might be tempted to mock every single method in an interface. Don't
    do that. Only mock the methods that you actually use. That way, if the
    code is updated to call a method you didn't mock, your test with fail
    with a "Method not found" error.

LIMITATIONS
    Be aware that while mock objects can be useful, there are several
    limitations to be aware of.

  Interface Changes
    In theory, objects should be open for extension, closed for
    modification.

    In practice, we have deadlines, we make mistakes, needs evolve,
    whatever. If your mock object mocks an instance of "Foo::Bar" and you
    install a new version of "Foo::Bar" with a different interface, your
    mock may very well hide the fact that your code is broken.

  Encapsulation Violations
    Constantly you see developers do things like this:

        # don't reach inside!
        my $name = $object->{name};

    And:

        # this should be an ->isa check
        if ( ref $object eq 'Toy::Soldier' ) {
            ...
        }

    Both of those will fail with "Test::Mock::Object". This is by design to
    avoid the temptation to ignore these issues. This might mean that
    "Test::Mock::Object" is not suitable for your needs.

  We Changes Instances, Not Classes
    Thus, if you mock an instance of a base class, subclasses won't see that
    (and other instances won't see that either). Instead, you might find
    Mock::Quick useful. Test::MockModule might also help, or if you just
    need to replace one or two methods in a lexical scope, see
    Sub::Override.

NOTES
  Memory Leak Protection
    If you install Test::LeakTrace, a test in our test suite will verify
    that we do not have memory leaks. I've only tested this on a couple of
    versions of Perl. It's possible that some versions will leak. Please let
    me know if this happens.

  Chained Methods
    Method chains are often a code smell. You can read about The Law of
    Demeter <https://en.wikipedia.org/wiki/Law_of_Demeter> for more
    information. However, breaking a chain sometimes means creating a series
    of mocks for each method in the chain. So we support method chains.

    This *is* a code smell. Method chains are fragile. Instead of this:

        my $office = $customer->region->sales_rep->office;

    Consider this:

        # in the Customer class
        sub regional_office ($self) {
            return $self->region->sales_rep->office;
        }

    And then you can just call:

        my $office = $customer->regional_office;

    If the office is then moved directly to the region instead of the
    salesperson, you can change that method to:

        sub regional_office ($self) {
            return $self->region->office;
        }

    And your code doesn't break instead of hunting down all of the offending
    method chains. (Of course, you would do this in all the places where you
    need to break those chains).

    That being said, it's often more work than you have time for, so this
    module provides method chains. Sadly, it's again the difference between
    theory and practice.

SEE ALSO
    *   Test::MockObject

        I used this years ago when chromatic first wrote it for the company
        we worked at. I've used it off and on over the years and I *never*
        remember its interace.

    *   Mock::Quick

        This one is actually pretty good, but still does a bit more than I
        want, and doesn't support method chains.

    *   Test2::Tools::Mock

        This is the successor to Mock::Quick and is included with Test2. If
        you have Test2 installed, you don't need to install another
        dependency.

    *   Test::MockModule

        Another useful module whose interface I find cumbersome, but it uses
        a completely different approach from this module.

    *   Test::Mock::Apache2

        This was missing some methods I needed and is what finally led me to
        write this module.

AUTHOR
    Curtis "Ovid" Poe <ovid@allaroundtheworld.fr>

COPYRIGHT AND LICENSE
    This software is Copyright (c) 2021 by Curtis "Ovid" Poe.

    This is free software, licensed under:

      The Artistic License 2.0 (GPL Compatible)

