3

The only help I've found on the internet so far is this blog. Which I thought was going to get me there, but I don't think it's actually changing the values in my module. I made a sample to show what I mean.

package Module;

use 5.012;
use strict;
use warnings;
use Readonly   qw( );

use parent     qw(Exporter);
our @EXPORT_OK = qw(
   &GetReadonly
);
our %EXPORT_TAGS = (
   all => [ @EXPORT_OK ] );

Readonly::Scalar my $HOST => 'host.web.server.com';

sub GetReadonly
{
   return $HOST;
}

1;

And the test code:

#!perl

use strict;
use warnings;
use Test::More 'no_plan';
use Module qw/ :all /;

is($Module::HOST, 'host.web.server.com');     # HOST == undef

my $fake_host = 'fakemail.web.server.com';

{
   no warnings 'redefine';
   local *Readonly::Scalar::STORE = sub { ${$_[0]} = $_[1]; };
   $Module::HOST = $fake_host;
}

is(GetReadonly(), $fake_host);      # Returns host.web.server.com

If I use the Module::HOST from the blog, I get a bareword compile error.

Is there some better way to mock a Readonly for a unit test?

Eric Fossum
  • 2,395
  • 4
  • 26
  • 50

1 Answers1

4

The blog was probably written in the old days when Readonly was implemented in pure Perl using tie. Nowadays, Readonly is implemented using XS. To make a variable not readonly anymore, you can call

Internals::SvREADONLY( $Module::HOST, 0 );

To be able to access the variable from outside of the module, it must be declared our, not my (as the blog correctly shows).

But the main question is: why do you need to test a different value in the variable, if the variable isn't supposed to be writable?

choroba
  • 231,213
  • 25
  • 204
  • 289
  • I am unit testing, so I want access to private things. In this case, I want to change the mail server and validate an error condition. We don't normally want this information public, so I'd rather not make it an our variable if I don't have to. – Eric Fossum Nov 23 '20 at 16:06
  • In case you're wondering, if I change the variable to our, your solution works great. If there's anyway to keep it a scoped within the module, I'd love to hear it. – Eric Fossum Nov 23 '20 at 16:08
  • @EricFossum What's the difference between exposing the variable directly via `our`, or exposing it via the exported function `GetReadOnly()`? – stevieb Nov 23 '20 at 16:25
  • That function was just for testing if I set it. Nothing returns that value in my prod. code. – Eric Fossum Nov 23 '20 at 16:36
  • What error condition do you try to validate? Wouldn't it be better to mock a connection/request? – choroba Nov 23 '20 at 16:51
  • I think I could mock the `Net::SMTP->new` call, just trying to use as much of the real function as possible. The bad host request is coming back pretty much instantly, so I wasn't too worried about timeouts. – Eric Fossum Nov 23 '20 at 18:11
  • If your production code uses the `GetReadonly` function to access the value, you can overwrite that in your test. – simbabque Nov 24 '20 at 15:31