Reputation: 28774
I have some unit test which depends on environment variable.
I'd like to unset these environment variable before testing using maven.
Question: How can I achieve that?
Upvotes: 5
Views: 6604
Reputation: 27812
Unfortunately, in this case you cannot use the environmentVariables
option of the Maven Surefire Plugin, mainly because it would only work to add new environment variables but not override (or reset, which is actually equals to override to empty value) an existing variable.
Also note: an ant run wrapped in Maven and executed before the test would not work either.
The proposed solution is based on Java, especially on this approach proposed in another SO post. Although not advisable for application code, this hack may be acceptable for test code.
You could add the following class to your test code (under src/test/java
):
package com.sample;
import java.lang.reflect.Field;
import java.util.Map;
public class EnvHack {
@SuppressWarnings("unchecked")
public static void resetEnvironmentVariable(String name, String value) throws Exception {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.put(name, value);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass
.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.put(name, value);
}
public static void main(String[] args) throws Exception {
resetEnvironmentVariable("test_prop", "test_value");
}
}
The class above is basically hacking Java API to change in memory the values of the environment variables. As such, they can be set to different values, reset and even unset actually (remove it from the map).
Since the class is now part of your test code, you have several options:
@Before
methods (or @BeforeClass
, with a certain difference) of a certain JUnit test case (before every JUnit method of the concerned class)Let's have a look at each possible solution.
Use the class in the @Before
methods of a certain JUnit test case
package com.sample;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MainTest {
@Before
public void init() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value");
}
@Test
public void testEnvProperty() throws Exception {
String s = System.getenv("test_prop");
Assert.assertEquals(s, "test_value");
}
}
This solution can be used per test class and when a set of tests (methods) share the same requirements (suggestion: if they don't, it may be an hint, probably some method should be moved out).
Use it within a JUnit test method
package com.sample;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MainTest {
@Before
public void init() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value");
}
@Test
public void testEnvProperty() throws Exception {
EnvHack.resetEnvironmentVariable("test_prop", "test_value2");
String s = System.getenv("test_prop");
Assert.assertEquals(s, "test_value2");
}
}
This solution gives the highest freedom: you can play with exactly the concerned variable exactly where required, although may suffer of code duplication it could also enforce tests isolation.
Run its main method before any executed JUnit test
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<phase>process-test-classes</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.sample.EnvHack</mainClass>
<classpathScope>test</classpathScope>
</configuration>
</execution>
</executions>
</plugin>
Note what we are doing in this case:
java
goal of the Exec Maven Plugin to actually invoke the mainClass
of our env hack class.classPathScope
set to test
in order to make it visible to the Enforcer Pluginprocess-test-classes
phase just to make sure it is executed before the test
phase and hence before any test.This solution centralizes the whole prepare environment procedure, once for all tests.
On the other side, you may also consider to use mocking in your tests. This is not a centralized option (unless you code it) but could give further hints and hence worth to mention. Here is a sample code resetting an environment variable via PowerMock
package com.sample;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(System.class)
public class EnvTest {
@Before
public void init() {
PowerMockito.mockStatic(System.class);
Mockito.when(System.getenv("TEST_PROP")).thenReturn("TEST_VALUE");
}
@Test
public void test() {
String var = System.getenv("TEST_PROP");
Assert.assertEquals("TEST_VALUE", var);
}
}
This is similar to the first and second approach proposed above.
Note that to make it work you need to add the following dependencies to your POM:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>
</dependencies>
General note: indeed having tests which are not fully isolated and depends on the existence (or the absence) of an environment variable is not advisable. You may run into maintenance issues or have more nasty troubleshooting, for colleagues or for your future yourself. So, if you really need it, better to properly document it.
Upvotes: 2