Jayan
Jayan

Reputation: 18468

unit testing with code that depends on environment variable

We have small set of environment variables exposed to control our application behavior. Application code is in java. (Environment variables may be evil, but the have a purpose in this special case)

What is the best way to unit test this? How can I create environment variables, set it to different values and call the tests repeatedly?

Current solution is to use env element in junit tasks . For example below

 <env key="BASE_PLUGINS_FOLDER" value="c:/temp"/>

code sets environment variable for the test. (This works only when fork mode is set to true)

This means I have to create multiple test sections in ant build file and corresponding test else where. This could become unmanageable pretty fast.

I feel there could be a better way, that is fully a junit-java code. Not sure how.

Upvotes: 3

Views: 4361

Answers (4)

Ashley Frieze
Ashley Frieze

Reputation: 5468

This can be solved using System Stubs (https://github.com/webcompere/system-stubs) in JUnit 4 and JUnit 5:

JUnit 4:

@Rule
public EnvironmentVariablesRule env = new EnvironmentVariablesRule("BASE_PLUGINS_FOLDER", "/tmp/something");

@Test
public void test() {
   // variable is set here
}

Or in JUnit 5:

@ExtendWith(SystemStubsExtension.class)
class SomeTest {

    @SystemStub
    private EnvironmentVariables env = new EnvironmentVariables("BASE_PLUGINS_FOLDER", "/tmp/something");

    @Test
    void envSetHere() {
        // environment variable is set
    }
}

Upvotes: 0

Jayan
Jayan

Reputation: 18468

Here is a hack given by my friend..

            Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
        Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
        theEnvironmentField.setAccessible(true);
        @SuppressWarnings({"unchecked"}) // this will be a Map or the test will fail if Java internals are changed
                Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
        env.put(name, val);

        // now System.getenv will return what we want
        //noinspection CallToSystemGetenv
        assertEquals("Problems setting up environment variable for test: " + name, val, System.getenv(name));

Upvotes: 3

Cedric Beust
Cedric Beust

Reputation: 15608

You should encapsulate how you retrieve these variables. Not only will it allow you to change the way you pass them around (env variable, system properties, configuration file, etc...) but it will also more testable.

Then you can define two different implementations: one that actually reads the environment (which you use in production) and one where you can specify these values yourself in Java (which you use in your tests).

Upvotes: 8

Ed Staub
Ed Staub

Reputation: 15700

I fear this is a useless, frustrating answer... but consider redesigning the app to allow overriding the environment variables.

All access to them would need to be funneled through one place, which would also look for your choice of config mechanism -
-D,
or property file,
or property file pointed at by -D,
or runtime-created config object passed by test setup,
or smoke signals,
or ...

Upvotes: 1

Related Questions