testing advanced

How to mock objects with Test::Mock::Simple in Perl?

Question

How to mock objects with Test::Mock::Simple in Perl?

How to Mock Objects in Perl Without Test::Mock::Simple

The module Test::Mock::Simple is convenient for lightweight mocking but is not part of the Perl core distribution. Since you cannot install external modules in your sandbox environment, a practical alternative is to create mock objects manually using Perl’s built-in features.

Perl’s flexible object model allows you to easily create mock objects by blessing hashes (or other references) and providing methods as code references (subroutines). This respects the TMTOWTDI philosophy—there’s more than one way to do it. You can track method calls, arguments, and return values in your mock with plain Perl.

Key Concepts

  • Manual mocks: Bless a hash and add methods as subroutine references.
  • Method signatures: Typically, methods receive the mock object as $self followed by arguments.
  • Tracking calls: Store call count and arguments inside the mock data structure.
  • No dependencies: Uses only core Perl features, ensuring sandbox compatibility.

Example: Manual Mock Object with Call Tracking

The example below demonstrates how you can create a mock object that simulates a fetch_data method. This mock tracks calls, prints info, and returns preset values—all without any external modules.

use strict;
use warnings;

# Simulate a real class
package Real::Service;
sub new { bless {}, shift }
sub fetch_data {
    my ($self, $param) = @_;
    # Pretend to fetch remote data
    return "Real data for $param";
}
1;

package main;

# Create a manual mock object
sub create_mock {
    my %mock_data = (
        calls => {},  # e.g. fetch_data => count
        last_args => {}, # e.g. fetch_data => \@args
    );

    # The mock object is a blessed hashref
    my $mock = bless {
        calls => \%{ $mock_data{calls} },
        last_args => \%{ $mock_data{last_args} },
    }, "Mock::Service";

    # Define the fetch_data mock method as a closure
    no strict 'refs';
    *{"Mock::Service::fetch_data"} = sub {
        my ($self, @args) = @_;
        $self->{calls}->{fetch_data}++;
        $self->{last_args}->{fetch_data} = [@args];
        print "Mock fetch_data called with param='$args[0]'\n";
        return "Mocked data for $args[0]";
    };

    return $mock;
}

# Instantiate mock
my $mock = create_mock();

# Call mocked method
my $data = $mock->fetch_data("test");
print "Returned: $data\n";

# Check call count and last arguments
my $count = $mock->{calls}->{fetch_data} || 0;
print "fetch_data was called $count times\n";

my $last_args = $mock->{last_args}->{fetch_data} || [];
print "Last call arguments: ", join(", ", @$last_args), "\n";

Explanation

  • The mock is a blessed hash storing call counts and last arguments.
  • We install a method fetch_data dynamically in the mock package namespace.
  • Each call increments counts and saves the args, providing basic call tracking.
  • The method prints a message and returns a canned response.
  • This approach mimics key features of Test::Mock::Simple but uses no external dependencies.

Common Pitfalls

  • Method namespace: Make sure your mock’s methods are installed in the correct package.
  • Call tracking scope: Store call-related data in the mock object itself to avoid collisions.
  • Subroutine aliasing: Use no strict 'refs' carefully when defining methods dynamically.
  • Matching interfaces: Your mock methods should accept the same parameters as the originals.

Summary

While mocking frameworks like Test::Mock::Simple simplify mocking, you can easily build your own lightweight mocks with core Perl features in environments that prohibit CPAN modules. Blessing a hash and installing anonymous subs as methods provides flexible, transparent mocks. This technique allows you to track calls, simulate behavior, and effectively isolate your tests without extra dependencies.

Verified Code

Executed in a sandbox to capture real output. • v5.34.1 • 23ms

Tip: edit code and use “Run (Browser)”. Server runs always execute the published, verified snippet.
STDOUT
Mock fetch_data called with param='test'
Returned: Mocked data for test
fetch_data was called 1 times
Last call arguments: test
STDERR
(empty)

Was this helpful?

Related Questions