How do I mock a realine of a GLOB ref when unit testing in perl?

Viewed 78

For example in the following code how do I mock readline to unit test the do_stuff routine?

I think I require

  1. $sock must be a GLOB reference.
  2. readline() must be mocked.

I tried using FileHandle->new but got error - "readline() on unopened filehandle GEN1" which I took as readline() wasn't being mocked.

I tried an Test::MockObject (not extends), with readline() mocked, but that had the error "Not a GLOB reference" which reminded me that readline() isn't being called as a method.

package MyIO;

sub get_sock{ return IO::Socket::INET->new(@tcp_arg); }

sub do_stuff {
    my $sock = get_sock();
    my $line1 = <$sock>;
    my $line2 = readline($sock);
    return;
}

package TestMyIO;

sub test_do_stuff{
    # my $mock_sock = Test::MockObject::Extends->new(IO::Socket::INET->new);

    my $mock_sock = die("TODO");
    my @fake_lines = ('line 1', 'line 2', 'line 3');

    no warnings 'redefine';    ## no critic (ProhibitNoWarnings)
    local *MyIO::get_sock = sub { return $mock_sock; };
    local *MyIO::readline = sub { return shift @fake_lines; };
    use warnings 'redefine';

}
2 Answers

You can use the following:

open(my $fh, '<', \$str);

This won't work if a system file handle is needed (e.g. if the handle needs to survive an exec). But as long as you stick to Perl, reads from the handle will read from the provided buffer.

Builtins like readline must be mocked in the CORE::GLOBAL namespace, and must generally be defined at compile-time.

my @fake_lines;
BEGIN {
    *CORE::GLOBAL::readline = sub { shift @fake_lines };
}
@fake_lines = ('line 1','line 2','line 3');
Related