choroba has asked for the wisdom of the Perl Monks concerning the following question:
I need to test a method that is defined in a large code base. I tried to minimise the example, it seems there are two important players: Moose::Exporter and namespace::autoclean.
The 2 following modules are old and have existed in the codebase for years:
lib/Logger.pm
lib/Common.pmpackage Logger; use warnings; use strict; use Exporter qw{ import }; our @EXPORT = qw{ log_warn }; sub log_warn { warn @_ } __PACKAGE__
package Common; use warnings; use strict; use experimental qw( signatures ); use Logger; use Moose::Exporter; my ($import, $unimport, $init_meta) = Moose::Exporter->build_import_me +thods( as_is => [\&Logger::log_warn] ); sub import { goto &$import } __PACKAGE__
And here's my new module:
lib/MyObj.pm
package MyObj; use Moose; use Common; use namespace::autoclean; use experimental qw{ signatures }; has value => (is => 'ro', required => 1); sub foo($self) { log_warn('Value too large') if $self->value > 10; } __PACKAGE__->meta->make_immutable
And now I need to test that the object with value 11 logs the warning when foo() is invoked. I started with the following code:
t/01-basic.t
#!/usr/bin/perl use warnings; use strict; use Test::More tests => 2 * 2; use Sub::Override; my @warnings; my $override; use MyObj; BEGIN { $override = 'Sub::Override'->new( 'MyObj::log_warn' => sub { push @warnings, $_[0] }); } for my $test ([1, undef, 'no warnings'], [11, 'Value too large', 'warn +ings']) { my ($value, $warnings, $name) = @$test; @warnings = (); my $o = bless {value => $value}, 'MyObj'; ok($o, 'constructs'); $o->foo; is($warnings[0], $warnings, $name); }
Unfortunately, the tests fail with
t/01-basic.t .. 1..4 Cannot replace non-existent sub (MyObj::log_warn) at t/01-basic.t line + 15. BEGIN failed--compilation aborted at t/01-basic.t line 16. # Looks like your test exited with 255 before it could output anything +. Dubious, test returned 255 (wstat 65280, 0xff00) Failed 4/4 subtests Test Summary Report ------------------- t/01-basic.t (Wstat: 65280 Tests: 0 Failed: 0) Non-zero exit status: 255 Parse errors: Bad plan. You planned 4 tests but ran 0.
Interestingly, removing use namespace::autoclean from MyObj.pm fixes the problem. But that's not what we want to do. I tried overriding the logger subroutine from the other modules, too, and at the end found a combination that worked:
t/01-basic.t
#!/usr/bin/perl use warnings; use strict; use Test::More tests => 2 * 2; use Sub::Override; my @warnings; my $override; use Logger; BEGIN { $override = 'Sub::Override'->new( 'Logger::log_warn' => sub { 'WHATEVER' }); } use MyObj; BEGIN { *MyObj::log_warn = sub { push @warnings, $_[0] }; } for my $test ([1, undef, 'no warnings'], [11, 'Value too large', 'warn +ings']) { my ($value, $warnings, $name) = @$test; @warnings = (); my $o = bless {value => $value}, 'MyObj'; ok($o, 'constructs'); $o->foo; is($warnings[0], $warnings, $name); }
I can use *Logger::log_warn = sub {...} as well, but I can't use Sub::Override for MyObj::low_warn.
Is there a way how to simplify the test so it's less hackish? Mocking just a single subroutine would be the best.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Mocking with namespace::autoclean and Moose::Exporter
by NERDVANA (Priest) on Nov 15, 2024 at 18:00 UTC | |
by choroba (Cardinal) on Nov 15, 2024 at 19:11 UTC | |
by NERDVANA (Priest) on Nov 15, 2024 at 23:14 UTC |