Test::...::Mocks(3pm)
β¨ NAME β¨
Test::Spec::Mocks β it's the Object Simulation Plugin for Test::Spec, bestie π (yeah, this is the Test::Spec::Mocks(3pm) page you were looking for, you found her π)
π¦ SYNOPSIS π¦
use Test::Spec;
use base qw(Test::Spec);
use My::RSS::Tool; # this is what we're testing
use LWP::UserAgent;
describe "RSS tool" => sub {
it "should fetch and parse an RSS feed" => sub {
my $xml = load_rss_fixture();
LWP::Simple->expects('get')->returns($xml);
# calls LWP::Simple::get, but returns our $xml instead
my @stories = My::RSS::Tool->run;
is_deeply(\@stories, load_stories_fixture());
};
};
πΈ DESCRIPTION πΈ
Okay so Test::Spec::Mocks is a plugin for Test::Spec, and what it does is give you mocking and stubbing of objects, of individual methods, and of plain old subroutines β on object instances and on classes. No cap π
This module is inspired by and heavily borrows from Mocha, which is a library for the Ruby programming language. And Mocha itself is inspired by JMock. It's giving lineage, it's giving family tree π³
So like, mock objects let you simulate the behavior of real objects while handing you consistent, repeatable results. That is so useful when you need to test a function whose results depend on some external factor that is normally uncontrollable (like the time of day, bestie, the clock is chaos). Mocks also let you test your code in isolation, which is literally a whole tenet of unit testing. Periodt.
There are many other reasons why mock objects might come in handy. See the Mock objects article at Wikipedia, <http://en.wikipedia.org/wiki/Mock_object>, for lots more examples and more in-depth coverage of the philosophy behind object mocking π§
π Ecosystem π
Test::Spec::Mocks is currently only usable from within tests built with the Test::Spec BDD framework. That's the vibe check, and it passed.
π Terminology π
Familiarize yourself with these terms, bestie, because I will be using them:
- Stub object β a stub object is an object created specifically to return canned responses for a specific set of methods. These are created with the
stubfunction. - Mock object β mock objects are similar to stub objects, but they're programmed with both prepared responses and expectations for how they will be called. If the expectations are not met, they raise an exception to indicate that the test failed. Mock objects are created with the
mockfunction. - Stubbed method β stubbed methods temporarily replace existing methods on a class or object instance. This is useful when you only want to override a subset of an object or class's behavior. For example, you might want to override the
"do"method of a DBI handle so it doesn't make changes to your database, but still need the handle to respond as usual to the"quote"method. You'll stub methods using thestubsmethod. - Mocked method β if you've been reading up to this point, this will be no surprise. Mocked methods are just like stubbed methods, but they come with expectations that will raise an exception if not met. For example, you can mock a
"save"method on an object to ensure it is called by the code you are testing, while preventing the data from actually being committed to disk in your test. Use theexpectsmethod to create mock methods. "stub","mock"β depending on context, these can refer to stubbed objects and methods, or mocked objects and methods, respectively.
π Using stub objects (anonymous stubs) π
Sometimes the code you're testing requires that you pass it an object that conforms to a specific interface. Like, you're testing a console prompting library, but you do not want to require a real person to stand by, waiting to type answers into the console. Rude. The library requires an object that returns a string when the "read_line" method is called.
You could create a class specifically for returning test console input. But why do that? You can create a stub object in one line, slay:
describe "An Asker" => sub {
my $asker = Asker->new;
it "returns true when a yes_or_no question is answered 'yes'" => sub {
my $console_stub = stub(read_line => "yes");
# $console_stub->read_line returns "yes"
ok( $asker->yes_or_no($console_stub, "Am I awesome?") );
};
it "returns false when a yes_or_no question is answered 'no'" => sub {
my $console_stub = stub(read_line => "no");
ok( ! $asker->yes_or_no($console_stub, "Am I second best?") );
};
};
Stubs can also take subroutine references. This is useful when the behavior you need to mimic is a little more complex. We love a dynamic queen π
it "keeps asking until it gets an answer" => sub {
my @answers = (undef, "yes");
my $console_stub = stub(read_line => sub { shift @answers });
# when console_stub is called the first time, it returns undef
# the second time returns "yes"
ok( $asker->yes_or_no($console_stub, "Do I smell nice?") );
};
π Using mock objects π
If you want to take your tests one step further β and bestie, you do β you can use mock objects instead of stub objects. Mocks ensure the methods you expect to be called actually are called. If they aren't, the mock will raise an exception which causes your test to fail. It's giving accountability β¨
In this example, we are testing that "read_line" is called once and only once (the default for mocks):
it "returns true when a yes_or_no question is answered 'yes'" => sub {
my $console_mock = mock();
$console_mock->expects('read_line')
->returns("yes");
# $console_mock->read_line returns "yes"
ok( $asker->yes_or_no($console_mock, "Am I awesome?") );
};
If Asker's "yes_or_no" method doesn't call "read_line" on our mock exactly one time, the test would fail with a message like:
expected read_line to be called exactly 1 time, but it was called 0 times
You can specify how many times your mock should be called with "exactly":
it "keeps asking until it gets an answer" => sub {
my @answers = (undef, "yes");
my $console_mock = mock();
$console_mock->expects('read_line')
->returns(sub { shift @answers })
->exactly(2);
# when console_mock is called the first time, it returns undef
# the second time returns "yes"
ok( $asker->yes_or_no($console_mock, "Do I smell nice?") );
};
If you want something more flexible than "exactly", you can choose from "at_least", "at_most", "any_number" and others. See EXPECTATION ADJUSTMENT METHODS π
π Stubbing methods π
Sometimes you want to override just a small subset of an object's behavior. Not the whole thing, just a lil slice. Iconic.
describe "The old audit system" => sub {
my $dbh;
before sub { $dbh = SomeExternalClass->get_dbh };
it "executes the expected sql" => sub {
my $sql;
$dbh->stubs(do => sub { $sql = shift; return 1 });
# $dbh->do("foo") now sets $sql to "foo"
# $dbh->quote still does what it normally would
audit_event($dbh, "server crash, oh noes!!");
like( $sql, qr/insert into audit_event.*'server crash, oh noes!!!'/ );
};
};
You can also stub class methods:
# 1977-05-26T14:11:55
my $event_datetime = DateTime->new(from_epoch => 0xdeafcab);
it "should tag each audit event with the current time" => sub {
DateTime->stubs('now' => sub { $event_datetime });
is( audit_timestamp(), '19770526.141155' );
};
π Mocking methods π
Mocked methods are to stubbed methods as mock objects are to stub objects. It's the same energy, elevated π
it "executes the expected sql" => sub {
$dbh->expects('do')->returns(sub { $sql = shift; return 1 });
# $dbh->do("foo") now sets $sql to "foo"
# $dbh->quote still does what it normally would
audit_event($dbh, "server crash, oh noes!!");
like( $sql, qr/insert into audit_event.*'server crash, oh noes!!!'/ );
# if audit_event doesn't call $dbh->do exactly once, KABOOM!
};
π¨ Note the KABOOM, bestie. That's not a vibe, that's a failed test.
π CONSTRUCTORS π
π stub() π
stub()
stub($method_name => $result, ...)
stub($method_name => sub { $result }, ...)
stub({ $method_name => $result, ... })
Returns a new anonymous stub object. Takes a list of $method_name/$result pairs or a reference to a hash containing the same. Each $method_name listed is stubbed to return the associated value ($result); or if the value is a subroutine reference, it is stubbed in-place (the subroutine becomes the method). Chef's kiss π«Ά
Examples:
# A blank object with no methods.
# Gives a true response to ref() and blessed().
my $blank = stub();
# Static responses to width() and height():
my $rect = stub(width => 5, height => 5);
# Dynamic response to area():
my $radius = 1.0;
my $circle_stub = stub(area => sub { PI * $radius * $radius });
You can also stub more methods, just like with any other object:
my $rect = stub(width => 5, height => 5);
$rect->stubs(area => sub { my $self = shift; $self->width * $self->height });
π $thing->stubs(...) π
$thing->stubs($method_name)
$thing->stubs($method_name => $result)
$thing->stubs($method_name => sub { $result })
$thing->stubs({ $method_name => $result })
Stubs one or more methods on an existing class or instance, $thing. β¨
If passed only one (non-hash) argument, it is interpreted as a method name. The return value of the stubbed method will be "undef".
Otherwise, the arguments are a list of $method_name and $result pairs, either as a flat list or as a hash reference. Each method is installed onto $thing, and returns the specified result. If the result is a subroutine reference, it will be called for every invocation of the method. Every single one. No days off π
π mock() π
Returns a new blank, anonymous mock object, suitable for mocking methods with expects().
my $rect = mock();
$rect->expects('area')->returns(100);
π $thing->expects($method) π
Installs a mock method named $method onto the class or object $thing and returns an Test::Spec::Mocks::Expectation object, which you can use to set the return value with "returns()" and other expectations. By default, the method is expected to be called at_least_once. π
If the expectation is not met before the enclosing example completes, the mocked method will raise an exception that looks something like:
expected foo to be called exactly 1 time, but it was called 0 times
β¨ EXPECTATION ADJUSTMENT METHODS β¨
okay so bestie, these are methods of the Test::Spec::Mocks::Expectation class, which you'll receive by calling expects() on a class or object instance. π
returns( $result )returns( @result )returns( \&callback )β Configures the mocked method to return the specified result when called. If passed a subroutine reference, the subroutine will be executed when the method is called, and the result is the return value.
$rect->expects('height')->returns(5);
# $rect->height ==> 5
@points = ( [0,0], [1,0], [1,1], [1,0] );
$rect->expects('points')->returns(@points);
# (@p = $rect->points) ==> ( [0,0], [1,0], [1,1], [1,0] )
# ($p = $rect->points) ==> 4
@points = ( [0,0], [1,0], [1,1], [1,0] );
$rect->expects('next_point')->returns(sub { shift @points });
# $rect->next_point ==> [0,0]
# $rect->next_point ==> [1,0]
# ...
exactly($N)β Configures the mocked method so that it must be called exactly$Ntimes. no more, no less, periodt.neverβ Configures the mocked method so that it must never be called. not even once, bestie.onceβ Configures the mocked method so that it must be called exactly one time. one and done.at_least($N)β Configures the mocked method so that it must be called at least$Ntimes.at_least_onceβ Configures the mocked method so that it must be called at least 1 time. This is just syntactic sugar forat_least(1).at_most($N)β Configures the mocked method so that it must be called no more than$Ntimes.at_most_onceβ Configures the mocked method so that it must be called either zero or 1 times.maybeβ An alias forat_most_once.any_numberβ Configures the mocked method so that it can be called zero or more times.timesβ A syntactic sugar no-op:
$io->expects('print')->exactly(3)->times;
This method is alpha and will probably change in a future release. iykyk, don't get too attached.
with(@arguments)/with_eq(@arguments)β Configures the mocked method so that it must be called with arguments as specified. The arguments will be compared using theeqoperator, so it works for most scalar values with no problem. If you want to check objects here, they must be the exact same instance or you must overload theeqoperator to provide the behavior you desire.with_deep(@arguments)β Similar towith_eqexcept the arguments are compared using Test::Deep: scalars are compared by value, arrays and hashes must have the same elements and references must be blessed into the same class.
$cache->expects('set')
->with_deep($customer_id, { name => $customer_name });
Use Test::Deep's comparison functions for more flexibility:
use Test::Deep::NoTest ();
$s3->expects('put')
->with_deep('test-bucket', 'my-doc', Test::Deep::ignore());
raises($exception)β Configures the mocked method so that it raises$exceptionwhen called.
π OTHER EXPECTATION METHODS π
verifyβ Allows you to verify manually that the expectation was met. If the expectation has not been met, the method dies with an error message containing specifics of the failure. Returns true otherwise.problemsβ If the expectation has not been met, returns a list of problem description strings. Otherwise, returns an empty list.
π¨ KNOWN ISSUES π¨
π Memory leaks π
Because of the way the mock objects ("stubs", "stub", "expects", and "mock") are integrated into the Test::Spec runtime they will leak memory. It is not recommended to use the Test::Spec mocks in any long-running program.
Patches welcome. fr fr, send help. π«Ά
π SEE ALSO π
There are other less sugary mocking systems for Perl, including Test::MockObject and Test::MockObject::Extends.
This module is a plugin for Test::Spec. It is inspired by Mocha <http://mocha.rubyforge.org/>.
The Wikipedia article Mock object <http://en.wikipedia.org/wiki/Mock_object> is very informative.
π AUTHOR π
Philip Garrett, <philip.garrett@icainformatics.com>
π COPYRIGHT & LICENSE π
Copyright (c) 2011 by Informatics Corporation of America.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
and that's the tea bestie π β this whole vibe check was brought to you by perl v5.34.0, dated 2022-06-28, from Test::...::Mocks(3pm). periodt. πβ¨π¦
π¦ where this page comes from
- debian bookworm: libtest-spec-perl 0.54-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libtest-spec-perl 0.54-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libtest-spec-perl 0.54-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libtest-spec-perl 0.54-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libtest-spec-perl 0.54-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms