Reputation: 347
I am trying to Unit-Test my module Importer::Git
that uses Git.pm
from CPAN and I would like to mock the calls to Git::command
,Git::repository
and Git::command_oneline
etc to not actually alter my filesystem. I tried to do this via Test::MockObject, but it seems I haven't fully understood the internal workings yet...
Example:
package Importer::Git
sub create_repository {
my ( $rc, $repo );
$rc = Git::command_oneline( 'init', $self->targetdir . "/" . $self->name );
$repo = Git->repository( Directory => $self->targetdir . "/" . $self->name );
$rc = $repo->command( 'config', 'user.name', $self->git_import_user->{ name } );
$self->_repo( $repo );
return $repo;
}
Testcase:
use Import::Git;
use Test::More tests => 1; # last test to print
use Test::Exception;
use Test::MockObject;
# pretend to have loaded the Git Module.
$INC{'Git.pm'} = 1;
my $git = Test::MockObject->new();
$git->fake_module('Git', repository => sub { $git } );
$git->set_true( qw(command command_oneline) );
$repo = Import::Git->init();
$repo->targetdir('./');
$repo->name('testrepo');
$repo->git_import_user({ name => 'test', email => '[email protected]', push_default => 'testpush' });
$repo->create_repository();
But it does not seem to replace the git object in question as this test crashes with the messages from the actual Git.pm Module.
error: Malformed value for push.default: testpush
error: Must be one of nothing, matching, simple, upstream or current.
fatal: bad config variable 'push.default' in file '/home/.../testrepo/.git/config' at line 10
init .//testrepo: command returned error: 128
I guess in those two lines from Importer::Git it doesnt replace $repo
$repo = Git->repository( Directory => $self->targetdir . "/" . $self->name );
$rc = $repo->command( 'config', 'user.name', $self->git_import_user->{ name } );
So how would I mock that properly? I would like the $repo->command
calls to just return 1.
UPDATE:
Daves guess was right. Correcting the code to this solved it:
use Test::More tests => 1; # last test to print
use Test::Exception;
use Test::MockObject;
my $git;
BEGIN {
# pretend to have loaded the Git Module.
$DB::single=1;
$INC{'Git.pm'} = 1;
$git = Test::MockObject->new();
$git->fake_module('Git', repository => sub { $git } );
$git->set_true( qw(command command_oneline) );
}
use Import::Git;
my $real_repo = Import::Git->init();
$real_repo->targetdir('./');
$real_repo->name('testrepo');
$real_repo->git_import_user({ name => 'test', email => '[email protected]', push_default => 'testpush' });
$real_repo->create_repository();
Upvotes: 3
Views: 327
Reputation: 54323
An alternative to Dave's excellent answer is to not use the fake_module
functionality at all, but instead temporarily overwrite the constructor using something like Sub::Override. I feel that gives more granulated control.
use Test::More;
use Test::MockObject;
use Sub::Override;
use Git (); # we need this so we can override it in case it hasn't been loaded yet
# test code ...
{
# this is our faked module
my $git = Test::MockObject->new;
$git->set_true( qw(command command_oneline) );
# we will save the arguments to the constructor here for
# inspection in tests later
my @constructor_args;
# this will temporarily replace the constructor until $sub
# goes out of scope
my $sub = Sub::Override->new(
'Git::new' => sub {
@constructor_args = @_; # save args
return $git; # return fake
}
);
is something_that_deals_with_git(), $what_you_expect, 'test stuff with git';
is scalar @constructor_args, 2, '... and Git constructor was called with 2 args';
# ...
}
It has the drawback that you cannot use Test::MockObject's facilities to look at the calls to new
, but there's an easy way to mitigate that with our @constructor_args
variable. Everything else stays the same.
Upvotes: 1
Reputation: 69244
This is just a guess and I don't have time to test it...
The documentation for the fake_module()
method says this:
Beware that this must take place before the actual module has a chance to load. Either wrap it in a BEGIN block before a use or require or place it before a
use_ok()
orrequire_ok()
call.
I expect that your module (is it called Import::Git
or Importer::Git?) loads
Git.pm, so you need to call
fake_module()` before loading your module. So something like this, perhaps:
use Test::More tests => 1; # last test to print
use Test::Exception;
use Test::MockObject;
my $git;
BEGIN {
# pretend to have loaded the Git Module.
$INC{'Git.pm'} = 1;
$git = Test::MockObject->new();
$git->fake_module('Git', repository => sub { $git } );
$git->set_true( qw(command command_oneline) );
}
use Import::Git;
Upvotes: 3