Vagnerr
Vagnerr

Reputation: 3007

Is there a way to override a Perl "use constant" in your unit testing?

I have a Perl module that I have declared some constants:

use constant BASE_PATH => "/data/monitor/";

In live operation the constant will never change but I wish to be able to modify it in my unit tests, e.g. to set it to ~/project/testdata/. Is there a way do do this without having to use global mutable variables?

Could I possibly use Test::MockObject on the constant?

Upvotes: 8

Views: 3196

Answers (5)

Evan Carroll
Evan Carroll

Reputation: 1

unconstant

One easy method to do this is to use unconstant.

# From ./lib/Foo.pm
package Foo {
    use constant BASE_PATH => '/data/monitor/';
}


# From test.pl
use unconstant;  # Do this first
use Foo;
use constant *Foo::BASE_PATH => '/otherData/otherMonitor/';

Disclaimer: I am the author of unconstant, but I had this very same issue.

Upvotes: 3

Schwern
Schwern

Reputation: 165200

Tests often reveal inflexibility in the design. This is one of those times. That constant shouldn't be constant.

If you're doing it for performance reasons I'm willing to bet hard currency that it doesn't make any difference.

Upvotes: 5

Ether
Ether

Reputation: 53986

package My::Class;

use constant BASE_PATH => "/a/real/value/";

# ..more code here..

1;

Now in your unit tests:

use My::Class;
*{My::Class::BASE_PATH} = sub { "/a/fake/value" };

# do your tests here...

Upvotes: 3

Massa
Massa

Reputation: 8972

Apparently, if your definition of BASE_PATH was used/compiled into another subroutine BEFORE your test does change it (via

*BASE_PATH = sub { ... }

or other stuff) you have no solution (because when the original module used BASE_PATH as a constant, it really defined an INLINE function that was, well, inlined when used in other code)

Upvotes: 1

Callum
Callum

Reputation: 2133

When using constants they are implemented as constant functions behaving something like:

use subs 'BASE_PATH';
sub BASE_PATH () {"/data/monitor/"}

Any uses of BASE_PATH in the program are inlined and so can't be modified.

To achieve similar you could manually use the subs pragma (to make BASE_PATH behave as a built in function) and declare BASE_PATH as a standard function:

use subs 'BASE_PATH';
sub BASE_PATH {"/data/monitor/"}

print "BASE_PATH is ".BASE_PATH."\n";

*BASE_PATH = sub {"/new/path"};
print "BASE_PATH is ".BASE_PATH."\n";

Although why you would want to do this I'm not too sure.

Upvotes: 6

Related Questions