|
NAMETest::Spec::Mocks - Object Simulation Plugin for Test::Spec 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());
};
};
DESCRIPTIONTest::Spec::Mocks is a plugin for Test::Spec that provides mocking and stubbing of objects, individual methods and plain subroutines on both object instances and classes. This module is inspired by and heavily borrows from Mocha, a library for the Ruby programming language. Mocha itself is inspired by JMock. Mock objects provide a way to simulate the behavior of real objects, while providing consistent, repeatable results. This is very useful when you need to test a function whose results are dependent upon an external factor that is normally uncontrollable (like the time of day). Mocks also allow you to test your code in isolation, a tenet of unit testing. There are many other reasons why mock objects might come in handy. See the Mock objects <http://en.wikipedia.org/wiki/Mock_object> article at Wikipedia for lots more examples and more in-depth coverage of the philosophy behind object mocking. EcosystemTest::Spec::Mocks is currently only usable from within tests built with the Test::Spec BDD framework. TerminologyFamiliarize yourself with these terms:
Using stub objects (anonymous stubs)Sometimes the code you're testing requires that you pass it an object that conforms to a specific interface. For example, you are testing a console prompting library, but you don't want to require a real person to stand by, waiting to type answers into the console. 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: 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. 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 objectsIf you want to take your tests one step further, 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. 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 methodsSometimes you want to override just a small subset of an object's behavior. 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 methodsMocked methods are to stubbed methods as mock objects are to stub objects. 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!
};
CONSTRUCTORS
EXPECTATION ADJUSTMENT METHODSThese are methods of the Test::Spec::Mocks::Expectation class, which you'll receive by calling expects() on a class or object instance.
OTHER EXPECTATION METHODS
KNOWN ISSUES
SEE ALSOThere 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. AUTHORPhilip Garrett, <philip.garrett@icainformatics.com> COPYRIGHT & LICENSECopyright (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.
|