Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Mocking files for reading

by mayaTheCat (Scribe)
on Sep 03, 2007 at 21:43 UTC ( [id://636784]=perlquestion: print w/replies, xml ) Need Help??

mayaTheCat has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I want to mock files in my tests. I could not find any module that could help. So I wrote my own: Test::MockInputFile.

However, I had a couple of problems:
. I was able to mock files only for reading. Because I cannot override the print builtin.
. I could not use a bareword as a file handler in the open and close builtins. This is most probably due to my lack of knowledge on globs and prototypes.

I have the following questions on this subject:
. Is there already a module which does this job?
. Is the module I have written worth putting on CPAN?
. Do you have any suggestions on the problems I face?

package Test::MockInputFile; use strict; # --- my %file; sub define_file_mocker { my ($name, $content) = @_; $content ||= ''; $file{$name} = $content; } sub undefine_file_mocker { my ($name, $content) = @_; delete $file{$name}; } # --- sub import { my ($class, %arg) = @_; my $package = caller; _export_functions_to($package); my $module = defined $arg{module} ? $arg{module} : $package; _override_open_and_close_builtins_for($module); } # --- sub _export_functions_to { my ($package) = @_; no strict 'refs'; *{"$package\::define_file_mocker"} = \&define_file_mocker; *{"$package\::undefine_file_mocker"} = \&undefine_file_mocker; } # --- sub _override_open_and_close_builtins_for { my ($module) = @_; no strict 'refs'; *{"$module\::open"} = sub (\$$;$) { my ($fh, $mode, $name) = @_; if ($mode =~ /^<$/) { } elsif ($mode =~ /^<\s*([^<>].*)$/) { $name = $1; } else { $name = $mode; } # --- if (! defined $file{$name}) { # indicating; 'No such file or directory' $! = 2; return 0; } $$fh = Test::MockInputFile::Handler->new($name); return 1; }; *{"$module\::close"} = sub (\$) { my ($fh) = @_; undef $$fh; }; } # --- { package Test::MockInputFile::Handler; use overload '<>' => \&next_line; sub new { my ($class, $name) = @_; bless { content => $file{$name}, }, $class; } sub next_line { my ($self) = @_; if (! defined $self->{content}) { return undef; } my $line; if (! defined $/) { $line = $self->{content}; $self->{content} = undef; } elsif ($self->{content} =~ m;(.+?$/);s) { $line = $1; $self->{content} = $'; $self->{content} = undef if $self->{content} eq ''; } else { $line = $self->{content}; $self->{content} = undef; } return $line; } } # --- 1;
Thanks,
Oguz

---------------------------------
life is ... $mutation = sub { $_[0] =~ s/(.)/rand()<0.1?1-$1:$1/ge };

Replies are listed 'Best First'.
Re: Mocking files for reading
by almut (Canon) on Sep 03, 2007 at 22:46 UTC

    I think the closest you can get is using a tied filehandle. This would nicely get you around all the problems like print not being overridable, etc.   See this node for a simple example.

Re: Mocking files for reading
by grinder (Bishop) on Sep 04, 2007 at 08:14 UTC
    Is there already a module which does this job?

    As far as I know, no.

    Is the module I have written worth putting on CPAN?

    Absolutely, and the name is right, too.

    I was able to mock files only for reading. Because I cannot override the print builtin.

    You can if you use a tied variable. You want to look at the PRINT routine in perltie for inspiration. Test::Output::Tie may be of some use as well.

    • another intruder with the mooring in the heart of the Perl

Re: Mocking files for reading
by FunkyMonk (Chancellor) on Sep 03, 2007 at 22:55 UTC
    I can't offer you any specific advice (I don't think you've told us enough for that), so consider this general advice that may, or may not, apply

    • Can you override the function that prints? That's what I did the last time I had a problem similar to yours.
    • I'm a (fairly recent) big fan of test-driven development. If you can't work out how to test a method/subroutine, you need to think more about how you can make the subroutine easier to test
    If you consider the second point, it means you think about your test first and then code it. Then write some code that makes your program pass the test.

    There's some really good reading in Extreme Perl.

Re: Mocking files for reading
by jczeus (Monk) on Sep 04, 2007 at 14:38 UTC
    Just a thought: could you mock your files with "in-memory" files (perl >= 5.8) ?
    my $str = ''; open my $fh, '+>', \$str or die $!; print {$fh} "$_\n" for qw( foo bar baz ); seek $fh, 0, SEEK_SET; print <$fh>;
Re: Mocking files for reading
by mayaTheCat (Scribe) on Sep 04, 2007 at 22:36 UTC
    Thanks to all of you for your enlightening replies.

    With your replies I realized that I have somehow neglected "tie" so far. Shame on me :)

    My aim is to write a module which simply helps testing reading or writing to files, without actually touching the file system.

    With that in my mind, it looks like in-memory file is the answer. Besides, when I play with it I concluded that the in-memory file mechanism is something like a predefined tied filehandle.

    I modified my module and wanna share with you: (As you can see, since the in-memory file mechanism is so powerful, I can also mock files for writing; thus I changed the name of the module)

    I plan to put it on CPAN. Any comments are welcome.

    Thanks again,
    Oguz

    ---------------------------------
    life is ... $mutation = sub { $_[0] =~ s/(.)/rand()<0.1?1-$1:$1/ge };

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://636784]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (6)
As of 2024-04-16 07:19 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found