BioPerl-Network
view release on metacpan or search on metacpan
t/lib/Test/Exception.pm view on Meta::CPAN
=head1 DESCRIPTION
This module provides a few convenience methods for testing exception based code. It is built with L<Test::Builder> and plays happily with L<Test::More> and friends.
If you are not already familiar with L<Test::More> now would be the time to go take a look.
You can specify the test plan when you C<use Test::Exception> in the same way as C<use Test::More>. See L<Test::More> for details.
=cut
sub _try_as_caller {
my $coderef = shift;
eval { uplevel 3, $coderef };
return $@;
};
sub _is_exception {
my $exception = shift;
return ref $exception || $exception ne '';
};
sub _exception_as_string {
my ( $prefix, $exception ) = @_;
return "$prefix normal exit" unless _is_exception( $exception );
my $class = ref $exception;
$exception = "$class ($exception)"
if $class && "$exception" !~ m/^\Q$class/;
chomp $exception;
return "$prefix $exception";
};
=over 4
=item B<dies_ok>
Checks that a piece of code dies, rather than returning normally. For example:
sub div {
my ( $a, $b ) = @_;
return $a / $b;
};
dies_ok { div( 1, 0 ) } 'divide by zero detected';
# or if you don't like prototypes
dies_ok( sub { div( 1, 0 ) }, 'divide by zero detected' );
A true value is returned if the test succeeds, false otherwise. On exit $@ is guaranteed to be the cause of death (if any).
The test description is optional, but recommended.
=cut
sub dies_ok (&;$) {
my ( $coderef, $description ) = @_;
my $exception = _try_as_caller( $coderef );
my $ok = $Tester->ok( _is_exception($exception), $description );
$@ = $exception;
return $ok;
}
=item B<lives_ok>
Checks that a piece of code exits normally, and doesn't die. For example:
sub read_file {
my $file = shift;
local $/;
open my $fh, '<', $file or die "open failed ($!)\n";
$file = <FILE>;
return $file;
};
my $file;
lives_ok { $file = read_file('test.txt') } 'file read';
# or if you don't like prototypes
lives_ok( sub { $file = read_file('test.txt') }, 'file read' );
Should a lives_ok() test fail it produces appropriate diagnostic messages. For example:
not ok 1 - file read
# Failed test (test.t at line 15)
# died: open failed (No such file or directory)
A true value is returned if the test succeeds, false otherwise. On exit $@ is guaranteed to be the cause of death (if any).
The test description is optional, but recommended.
=cut
sub lives_ok (&;$) {
my ( $coderef, $description ) = @_;
my $exception = _try_as_caller( $coderef );
my $ok = $Tester->ok( ! _is_exception( $exception ), $description );
$Tester->diag( _exception_as_string( "died:", $exception ) ) unless $ok;
$@ = $exception;
return $ok;
}
=item B<throws_ok>
Tests to see that a specific exception is thrown. throws_ok() has two forms:
throws_ok BLOCK REGEX, TEST_DESCRIPTION
throws_ok BLOCK CLASS, TEST_DESCRIPTION
In the first form the test passes if the stringified exception matches the give regular expression. For example:
throws_ok { read_file( 'unreadable' ) } qr/No file/, 'no file';
If your perl does not support C<qr//> you can also pass a regex-like string, for example:
throws_ok { read_file( 'unreadable' ) } '/No file/', 'no file';
The second form of throws_ok() test passes if the exception is of the same class as the one supplied, or a subclass of that class. For example:
throws_ok { $foo->bar } "Error::Simple", 'simple error';
Will only pass if the C<bar> method throws an Error::Simple exception, or a subclass of an Error::Simple exception.
You can get the same effect by passing an instance of the exception you want to look for. The following is equivalent to the previous example:
my $SIMPLE = Error::Simple->new;
throws_ok { $foo->bar } $SIMPLE, 'simple error';
Should a throws_ok() test fail it produces appropriate diagnostic messages. For example:
not ok 3 - simple error
# Failed test (test.t at line 48)
# expecting: Error::Simple exception
# found: normal exit
Like all other Test::Exception functions you can avoid prototypes by passing a subroutine explicitly:
throws_ok( sub {$foo->bar}, "Error::Simple", 'simple error' );
A true value is returned if the test succeeds, false otherwise. On exit $@ is guaranteed to be the cause of death (if any).
A description of the exception being checked is used if no optional test description is passed.
=cut
sub throws_ok (&$;$) {
my ( $coderef, $expecting, $description ) = @_;
croak "throws_ok: must pass exception class/object or regex"
unless defined $expecting;
$description = _exception_as_string( "threw", $expecting )
unless defined $description;
my $exception = _try_as_caller( $coderef );
my $regex = $Tester->maybe_regex( $expecting );
my $ok = $regex
? ( $exception =~ m/$regex/ )
: eval {
$exception->isa( ref $expecting ? ref $expecting : $expecting )
};
$Tester->ok( $ok, $description );
unless ( $ok ) {
$Tester->diag( _exception_as_string( "expecting:", $expecting ) );
$Tester->diag( _exception_as_string( "found:", $exception ) );
};
$@ = $exception;
return $ok;
};
=item B<lives_and>
Run a test that may throw an exception. For example, instead of doing:
my $file;
lives_ok { $file = read_file('answer.txt') } 'read_file worked';
is $file, "42", 'answer was 42';
You can use lives_and() like this:
lives_and { is read_file('answer.txt'), "42" } 'answer is 42';
# or if you don't like prototypes
lives_and(sub {is read_file('answer.txt'), "42"}, 'answer is 42');
Which is the same as doing
is read_file('answer.txt'), "42\n", 'answer is 42';
unless C<read_file('answer.txt')> dies, in which case you get the same kind of error as lives_ok()
not ok 1 - answer is 42
# Failed test (test.t at line 15)
# died: open failed (No such file or directory)
A true value is returned if the test succeeds, false otherwise. On exit $@ is guaranteed to be the cause of death (if any).
The test description is optional, but recommended.
=cut
sub lives_and (&;$) {
my ( $test, $description ) = @_;
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $ok = \&Test::Builder::ok;
no warnings;
local *Test::Builder::ok = sub {
$_[2] = $description unless defined $_[2];
$ok->(@_);
};
use warnings;
eval { $test->() } and return 1;
};
my $exception = $@;
( run in 3.547 seconds using v1.01-cache-2.11-cpan-75ffa21a3d4 )