John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

I'm looking at Test::More, and there is nothing in there to test that some expression is supposed to die with an exception. Is there some other Testharness-compatible module that helps with this?

I put together an example of an effective test:

my $test_name= "no such bundle"; try { HTML::EntityReference::ordinal('CapitalDifferentialD', ':fooXX'); fail ($test_name); } catch { my $exception= $_; like ($exception, qr/:fooXX/, "$test_name - mentions cause of erro +r"); like ($exception, qr/at t\/tests\.t/, "$test_name - carps correctl +y"); pass ($test_name); } ;
The same name is used in the pass and fail so we know they match. It makes sure the exception branch is taken.

It also performs tests on the "die" string. I'd like to get the full file name and line number of the try'ed expression, but making sure the carp string gives this file is enough.

So if this were canned somehow, I need to specify the code to run, and sub-tests on the exception with suitable labels.

If there is nothing like this already, how have people been testing? How might I reuse this construct so I don't have to duplicate so much for every one I want to check?

Update:

With the suggestion of Test::Exception, my code becomes

my $test_name= "no such bundle"; dies_ok { HTML::EntityReference::ordinal('CapitalDifferentialD', ':foo +XX') } $test_name; like ($@, qr/:fooXX/, "$test_name - mentions cause of error"); like ($@, qr/at t\/tests\.t/, "$test_name - carps correctly");
Saving the contents of the exception string and then testing it after the catch block addressed one of my concerns, with test cases being on a conditional path, and how to get them inside the canned construct.

But its use of $@ makes me worry that this doesn't handle modern (e.g. Moose) code, as with my own recent confusion as to $@ vanishing out from under me. It's fine for my immediate concern though.

Update again:

With the use of Test::Fatal, it becomes:

my $test_name= "no such bundle"; my $err= exception { HTML::EntityReference::ordinal('CapitalDifferenti +alD', ':fooXX') } ; ok (defined $err, $test_name); like ($err, qr/:fooXX/, "$test_name - mentions cause of error"); like ($err, qr/at t\/tests\.t/, "$test_name - carps correctly");
It's actually one more line, since exception doesn't itself report, and I have to save the exception in my own variable. That prevents problems with the ephemeral $@, but it is more noise. I wonder if my own implied global variable, or a localized $_, would be useful for something like that.

I also found that the use of carp within the code still reports the call at test.t, not within the file that contains the exception function. Even though the docs implied that, I guess it is using some other technique to mark the function as "safe" to carp even though it is still on the call stack.

—John

Replies are listed 'Best First'.
Re: testing the exception throwing
by Corion (Patriarch) on May 13, 2011 at 07:48 UTC
      No, I didn't try searching for "test exception". I guess I'm not used to thinking of it as a google-like general search, but just as a module name lookup. I also looked at lists of see-alsos in the testing tutorials and modules I have read (Simple and More).
Re: testing the exception throwing
by AnomalousMonk (Archbishop) on May 13, 2011 at 07:50 UTC
Re: testing the exception throwing
by chromatic (Archbishop) on May 13, 2011 at 20:01 UTC

    Test::Fatal is somewhat simpler, but likely more flexible. It avoids some of the concerns in your update.

      I see: it uses Try::Tiny itself, and just makes the exception into its own return value.

      It doesn't hide itself from the call stack, so it will make my Carp report show itself rather than my test function. Ah, but knowing that, I can just test for that instead, since the real test is that Carp reports on the caller, not some other intermediate function that was between the user-facing function and the one that died. It might be more helpful since the file/lineno will always be the same: that of the exception function itself. Perhaps a similar function can include a test of the exception string for mention of the correct location, as well.

      Thanks.

      —John