Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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?

I find myself in this same position. Please see my solution: 10 years late to the answer. stackoverflow.com/a/58920320/124486 Consider marking it as chosen if it's the best. – Evan Carroll Nov 18, 2019 at 17:54

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.

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.

I think it's useful to communicate to developers the constant won't change in the execution of the program, and to still allow testers to change this. This is pretty common. For example in C you could set a constant with custom logic using the preprocessor. I don't see it about preoptimization. He wants to say the base path is immutable and can't change, unless he wants to change it in a testing environment. – Evan Carroll Nov 18, 2019 at 19:26 @EvanCarroll It's really more about the inflexibly. If you need to change it in testing, you'll probably have other reasons; especially a path. To communicate its purpose wrap it in a configuration object. It can start as a simple wrapper around a hash and build on that: read from a config file, environment variables, or selecting the proper values for dev, testing, and production. – Schwern Nov 19, 2019 at 2:14 Would this work with subs in a module? For example: use myModule; *{myModule} = sub { "/a/fake/value" }; If not then is there a way to do this? – GuySoft Dec 24, 2013 at 13:53 @EvanCarroll My question was about subs in a module, can't see that in the unconstant docs. Also at the time I was editing code I could not really edit. Anyway this was 6 years ago and I have no access to that codebase – GuySoft Nov 19, 2019 at 11:35 @GuySoft well just to answer that question directly then, subs in a module can be redefined in perl from any package using the syntax provided by Ether. That is unless those subs are inlined. Then at the point you redefine them it's too late. Some modules like constant produce subs that get inlined. For those you need a little bit more, like unconstant. – Evan Carroll Nov 19, 2019 at 14:21

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)

The only solution is to monkey patch constant's import subroutine. Install a wrapper around constant's import that intercepts specified constant names to be overridden and the values to apply. If you do this, then only the overriden constant subs will be created and they will be inlined by the compiler. I don't have time to write something like this right now, but it shouldn't be too hard. – daotoad Jul 2, 2009 at 17:24 But in that case, making your own constant.pm that does not let the sub be inlined would be even better/easier, no? put it somewhere early in the test library path and ta-da... – Massa Jul 2, 2009 at 19:12

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.