package WebGUI::TestException; use strict; use Test::Builder; use WebGUI::Exception; use Sub::Uplevel qw( uplevel ); use Exporter qw(import); our @EXPORT = qw( throws_deeply ); =head1 NAME Package WebGUI::TestException =head1 DESCRIPTION This module provides a convenient way to test for thrown exceptions. The idea is based on Test::Exception, which does provide a means to test for a specific exception class, but cannot test attributes of that class, which is necessary in the WebGUI test suite. This module can do that. =head1 CAVEATS This module uses Sub::Uplevel. In Test::Exception some hocus pocus is being done with the caller() function. The functions _quiet_caller and _try_as_caller are directly copied from Test::Exception. I do not know why this hocuspocus is being in that module however, since doing 'eval { uplevel 1, $codeRef }' seems to work too. On my platform at least =). For the time being, I leave those subs in here so that they may be used. They are commented out by default, though. =cut #---------------------------------------------------------------------------- sub _quiet_caller (;$) { ## no critic Prototypes my $height = $_[0]; $height++; if( wantarray and !@_ ) { return (CORE::caller($height))[0..2]; } else { return CORE::caller($height); } } #---------------------------------------------------------------------------- sub _try_as_caller { my $coderef = shift; # local works here because Sub::Uplevel has already overridden caller local *CORE::GLOBAL::caller; { no warnings 'redefine'; *CORE::GLOBAL::caller = \&_quiet_caller; } eval { uplevel 3, $coderef }; return $@; }; =head2 throws_deeply ( $codeRef, $expectClass, $fields, $message ) Executes the code ref and verifies it throws an exception of the given class with the given fields. =head3 $codeRef The code ref containing the code to be evalled. =head3 $expectClass The class name the thrown exception should have. =head3 $fields Hashref containg the exception fields and their expected values. =head3 $message The message that should be displayed by prove for this test. =cut #---------------------------------------------------------------------------- sub throws_deeply { my $evalBlock = shift; my $expectClass = shift; my $fields = shift; my $message = shift; my $testBuilder = Test::Builder->new; # Dunno why uplevel 1 might not work and why caller is redefined. # Copied _try_as_caller and _quiet_caller are from Test::Exception. # Uplevel 1 seems to work though. #_try_as_caller( $evalBlock ); eval { uplevel 1, $evalBlock }; my $e = Exception::Class->caught(); my $gotClass = ref $e; # Check class unless ($gotClass eq $expectClass) { $testBuilder->ok(0, $message); $testBuilder->diag("Wrong class:\n\texpected : '$expectClass'\n\t got : '$gotClass'"); return 0; } # Check fields my $errors; foreach (keys %$fields) { my $result = $e->$_; unless ( $result eq $fields->{$_} ) { $errors .= "'$_' => \n\texpected : '".$fields->{$_}."'\n\t got : '$result'\n"; } } if ($errors) { $testBuilder->ok(0, $message); $testBuilder->diag("Fields do not match:\n$errors"); return 0; } # Test passed. $testBuilder->ok(1, $message); return 1; } 1;