読者です 読者をやめる 読者になる 読者になる

Perl の Mock ライブラリで import したサブルーチンをモックできるか

use Date::Calc qw/Today/;

こんな感じで import 済みの Today サブルーチンをモックしたいぜ、なんてことを思った時、 Perl なら symbol table 弄り系の Mock ライブラリでできちゃう、はず。と思って Test::MockModule で main の Today をモックしようとしたらできなかった。

Test::Mock::Guard ならできた。 Test::Mock::Guard 愛してます。

use strict;
use warnings;

use Test::More;
use Test::Exception;
use Date::Calc qw/Today/;

use Test::MockModule;
use Test::Mock::Guard qw/mock_guard/;

subtest mocked => sub {
    my $guard = mock_guard(
        'Date::Calc' =>
        {
            Today => sub {
                return 1;
            },
        },
        'main' =>
        {
            Today => sub {
                return 1;
            },
        },
    );

    is(Date::Calc::Today(), 1);
    is(Today(), 1);
};

subtest restored => sub {
    is(Date::Calc::Today(), 4);
    is(Today(), 4);
};

subtest mocked_by_mockmodule => sub {
    my $module = new Test::MockModule('Date::Calc');
    $module->mock(
        'Today',
        sub {
            return 1;
        }
    );
    dies_ok {
        my $module2 = new Test::MockModule('main');
        $module2->mock(
            'Today',
            sub {
                return 1;
            }
        );
    } 'die';

    is(Date::Calc::Today(), 1);
    is(Today(), 4);
};

subtest restored_by_mockmodule => sub {
    is(Date::Calc::Today(), 4);
    is(Today(), 4);
};

done_testing();

理由

Test::MockModule は、与えられたパッケージが既に use / require されているかどうか調べるのに、 "$module_name::VERSON" を見てる。 $main::VERSION なんてものはないので、ここでコケてしまう。

Test::Mock::Guard は、 Class::Load の is_class_loaded を使っていて、こいつがうまくやってくれるみたい。 XS なので先は読む気がしなかった!