From 1658f3957d5279085f54c747c9b007d433e42afc Mon Sep 17 00:00:00 2001 From: Martin Kamerbeek Date: Tue, 11 Mar 2008 22:07:21 +0000 Subject: [PATCH] Added WebGUI::TestException and convert Shop/Pay.t to use it. --- t/Shop/Pay.t | 122 +++++++++++++-------------------- t/lib/WebGUI/TestException.pm | 123 ++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 76 deletions(-) create mode 100644 t/lib/WebGUI/TestException.pm diff --git a/t/Shop/Pay.t b/t/Shop/Pay.t index 0618612f9..c2a2a199c 100644 --- a/t/Shop/Pay.t +++ b/t/Shop/Pay.t @@ -18,11 +18,13 @@ use strict; use lib "$FindBin::Bin/../lib"; use Test::More; use Test::Deep; +use Test::Exception; use JSON; use HTML::Form; use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; +use WebGUI::TestException; #---------------------------------------------------------------------------- # Init @@ -31,7 +33,7 @@ my $session = WebGUI::Test->session; #---------------------------------------------------------------------------- # Tests -my $tests = 27; +my $tests = 18; plan tests => 1 + $tests; #---------------------------------------------------------------------------- @@ -54,17 +56,15 @@ skip 'Unable to load module WebGUI::Shop::Pay', $tests unless $loaded; my $e; my $pay; -eval { $pay = WebGUI::Shop::Pay->new(); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'new takes an exception to not giving it a session variable'); -cmp_deeply( - $e, - methods( - error => 'Must provide a session variable', - got => '', - expected => 'WebGUI::Session', - ), - 'new: requires a session variable', + +throws_deeply ( sub { $pay = WebGUI::Shop::Pay->new(); }, + 'WebGUI::Error::InvalidObject', + { + error => 'Must provide a session variable', + got => '', + expected => 'WebGUI::Session', + }, + 'new takes an exception to not giving it a session variable' ); $pay = WebGUI::Shop::Pay->new($session); @@ -79,8 +79,6 @@ isa_ok($pay, 'WebGUI::Shop::Pay', 'new returned the right kind of object'); isa_ok($pay->session, 'WebGUI::Session', 'session method returns a session object'); is($session->getId, $pay->session->getId, 'session method returns OUR session object'); - - ####################################################################### # # addPaymentGateway @@ -89,60 +87,45 @@ is($session->getId, $pay->session->getId, 'session method returns OUR session ob my $gateway; -eval { $gateway = $pay->addPaymentGateway(); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'addPaymentGateway croaks without a class'); -cmp_deeply( - $e, - methods( - error => 'Must provide a class to create an object', - ), +throws_deeply ( sub { $gateway = $pay->addPaymentGateway(); }, + 'WebGUI::Error::InvalidParam', + { + error => 'Must provide a class to create an object' + }, 'addPaymentGateway croaks without a class', ); -eval { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::NoSuchDriver'); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'addPaymentGateway croaks without a configured class'); -cmp_deeply( - $e, - methods( +throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::NoSuchDriver'); }, + 'WebGUI::Error::InvalidParam', + { error => 'The requested class is not enabled in your WebGUI configuration file', param => 'WebGUI::Shop::PayDriver::NoSuchDriver', - ), + }, 'addPaymentGateway croaks without a configured class', ); -eval { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash'); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'addPaymentGateway croaks without a label'); -cmp_deeply( - $e, - methods( +throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash'); }, + 'WebGUI::Error::InvalidParam', + { error => 'Must provide a label to create an object', - ), + }, 'addPaymentGateway requires a label', ); -eval { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL'); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'addPaymentGateway croaks without options to build a object with'); -cmp_deeply( - $e, - methods( +throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL'); }, + 'WebGUI::Error::InvalidParam', + { error => 'You must pass a hashref of options to create a new PayDriver object', - ), + }, 'addPaymentGateway croaks without options to build a object with', ); -eval { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL', {}); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'addPaymentGateway croaks without options to build a object with'); -cmp_deeply( - $e, - methods( +throws_deeply ( sub { $gateway = $pay->addPaymentGateway('WebGUI::Shop::PayDriver::Cash', 'JAL', {}); }, + 'WebGUI::Error::InvalidParam', + { error => 'You must pass a hashref of options to create a new PayDriver object', - ), + }, 'addPaymentGateway croaks without options to build a object with', ); @@ -170,11 +153,7 @@ my $defaultPayDrivers = { 'WebGUI::Shop::PayDriver::Cash' => 'Cash', }; -cmp_deeply( - $drivers, - $defaultPayDrivers, - 'getDrivers returns the default PayDrivers', -); +cmp_deeply( $drivers, $defaultPayDrivers, 'getDrivers returns the default PayDrivers'); ####################################################################### # @@ -182,14 +161,11 @@ cmp_deeply( # ####################################################################### -eval { $drivers = $pay->getOptions(); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'getOptions takes exception to not giving it a cart'); -cmp_deeply( - $e, - methods( +throws_deeply( sub { $drivers = $pay->getOptions(); }, + 'WebGUI::Error::InvalidParam', + { error => 'Need a cart.', - ), + }, 'getOptions takes exception to not giving it a cart', ); @@ -199,26 +175,20 @@ cmp_deeply( # ####################################################################### -eval { $gateway = $pay->getPaymentGateway(); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'getPaymentDriver throws an exception when no paymentGatewayId is passed'); -cmp_deeply( - $e, - methods( +throws_deeply( sub { $gateway = $pay->getPaymentGateway(); }, + 'WebGUI::Error::InvalidParam', + { error => q{Must provide a paymentGatewayId}, - ), + }, 'getPaymentGateway throws exception without paymentGatewayId', ); -eval { $gateway = $pay->getPaymentGateway('NoSuchThing'); }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::ObjectNotFound', 'getPaymentGateway thows exception when a non-existant paymentGatewayId is passed'); -cmp_deeply( - $e, - methods( +throws_deeply( sub { $gateway = $pay->getPaymentGateway('NoSuchThing'); }, + 'WebGUI::Error::ObjectNotFound', + { error => q{payment gateway not found in db}, id => 'NoSuchThing', - ), + }, 'getPaymentGateway throws exception when called with a non-existant paymentGatewayId', ); diff --git a/t/lib/WebGUI/TestException.pm b/t/lib/WebGUI/TestException.pm new file mode 100644 index 000000000..7deae51fb --- /dev/null +++ b/t/lib/WebGUI/TestException.pm @@ -0,0 +1,123 @@ +package WebGUI::TestException; + +use strict; + +use Test::Builder; +use WebGUI::Exception; +use Sub::Uplevel qw( uplevel ); + +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; +