How to unset environment variable before testing in maven - java

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?

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:
Use the class in the #Before methods (or #BeforeClass, with a certain difference) of a certain JUnit test case (before every JUnit method of the concerned class)
Use it within a JUnit test method (custom and narrowed usage)
Run its main method before any executed JUnit test (in a more global way) as explained below (and probably answering the question, even though other scenarios are also worth to mention, imho).
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:
We are invoking the java goal of the Exec Maven Plugin to actually invoke the mainClass of our env hack class.
We are invoking it with classPathScope set to test in order to make it visible to the Enforcer Plugin
We are running it as part of the process-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.

Related

PowerMockito mock indirect static method

Class to test
public class Randomer {
public int get() {
return (int) Math.random() + 1;
}
}
The test class
package org.samiron;
import static org.junit.Assert.assertEquals;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.api.support.membermodification.MemberMatcher;
import org.powermock.api.support.membermodification.MemberModifier;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.testng.annotations.Test;
/**
* #author samiron
*
*/
#RunWith(PowerMockRunner.class)
#PrepareForTest({ Randomer.class, Math.class })
public class RandomerTest {
#Test
public void shouldAddUpDieRollsCorrectly() throws Exception {
PowerMockito.spy(Math.class);
MemberModifier.stub(MemberMatcher.method(Math.class, "random")).toReturn(2.0);
Randomer d = new Randomer();
assertEquals(3, d.get());
}
}
Always getting java.lang.AssertionError: expected:<3> but was:<1>
Whats going wrong here? To be honest, every time I came across a situation to mock a static function I try to find a way around instead of wasting time. So need your help to figure out the exact solution.
The sole purpose of the example classes is to demonstrate that Math.random() function is not mocked and therefore not returning the desired value.
General realization
Mocking is a every essential tool while writing tests. Although mocking on instances works quite as expected but mocking static methods seems to be a real complicated with so many combinations of mocking libraries and so many options to support just few simple scenarios. This should be streamlined.
Libraries used:
mockito-all-1.9.5.jar
powermock-mockito-release-full-1.5.1-full.jar
This test passes thereby proving that the static call Math.random() is successfully mocked:
#RunWith(PowerMockRunner.class)
// tell PowerMock about (a) the class you are going to test and (b) the class you are going to 'mock static'
#PrepareForTest({Randomer.class, Math.class })
public class RandomerTest {
#Test
public void shouldAddUpDieRollsCorrectly() throws Exception {
// prepare PowerMock for mocking statics on Math
PowerMockito.mockStatic(Math.class);
// establish an expectation for what Match.random() should return
PowerMockito.when(Math.random()).thenReturn(2.0);
Randomer d = new Randomer();
assertEquals(3, d.get());
}
}
The main differences between this and what you posted in your question are is that I am using ...
PowerMockito.mockStatic(Math.class) and PowerMockito.when(Math.random()).thenReturn(2.0)
... instead of:
PowerMockito.spy(Math.class) and MemberModifier.stub(MemberMatcher.method(Math.class, "random")).toReturn(2.0)
Also, in your OP the example code uses a mixture of JUnit (org.powermock.modules.junit4.PowerMockRunner) and TestNG (org.testng.annotations.Test) whereas in my example I am just using JUnit.
The above has been verified with
junit:4.12
powermock-module-junit4:1.7.0
powermock-api-mockito2:1.7.0
There seems to be a library mismatch here.
In the comments you stated to use the following dependencies (without the convenience of Maven):
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-mockito-release-full</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
I got your code working using these:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>

Fail tests on write to System.out or .err

I'd like my CI build to fail when the tests write something to System.out or System.err. Preferably I would like to have a list of tests which produced unwanted output.
I tried to use mavens surefire plugin with an added listener, but that does only part of the trick as you can see from the question JUnit RunListener is removed on fail()
Did someone else try something like this and are there other ways to achieve a cleaner console on CI builds?
You might have a look at this Maven plugin https://github.com/policeman-tools/forbidden-apis which check for forbidden API calls. You can define wich calls should be forbidden in the code.
edit Simple example to show exclusion of not permitted api calls. In the example the call of System.out.println should not be permitted in test classes with the exception of one test class.
pom.xml
<build>
<plugins>
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
<version>2.0</version>
<executions>
<execution>
<goals>
<goal>check</goal>
<goal>testCheck</goal>
</goals>
</execution>
</executions>
<configuration>
<signaturesFiles>
<signaturesFile>${project.build.testOutputDirectory}/signatures.txt</signaturesFile>
</signaturesFiles>
<excludes>
<!-- specify the classes which should be excluded -->
<exclude>**/Permitted*</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
The signatur file src\test\resources\signatures.txt
java.io.PrintStream#println(java.lang.String)
Test classes
// the one which doesn't use System.out
package sub.optimal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Assert;
import org.junit.Test;
public class NoSystemOutTest {
#Test
public void dummy() {
Logger logger = Logger.getGlobal();
logger.log(Level.INFO, "some info");
Assert.assertTrue(true);
}
}
.
// the one in which the use of System.out should be permitted
package sub.optimal;
import org.junit.Assert;
import org.junit.Test;
public class PermittedSystemOutTest {
#Test
public void dummy() {
System.out.println("permitted usage");
Assert.assertTrue(true);
}
}
.
// the one in which the use of System.out is not permitted
package sub.optimal;
import org.junit.Assert;
import org.junit.Test;
public class NotPermittedSystemOutTest {
#Test
public void dummy() {
System.out.println("not permitted usage");
Assert.assertTrue(true);
}
}
Running the check with mvn test-compile forbiddenapis:testCheck will only report the forbidden api usage in NotPermittedSystemOutTest.
[ERROR] Forbidden method invocation: java.io.PrintStream#println(java.lang.String)
[ERROR] in sub.optimal.NotPermittedSystemOutTest (NotPermittedSystemOutTest.java:9)
You can use the Checkstyle plugin so that your test with unwanted code fails and will be listed as failed in surfire reports.
You can check whether a test writes to System.out by using the library System Rules
public class YourTest {
#Rule
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
#After
public void assertNoTextHasBeenWrittenToSystemOut() {
assertEquals("", systemOutRule.getLog())
}
...
}
There same may be done with System.err by using the SystemErrRule.

PowerMock ECLEmma coverage issue

We are using EasyMock and PowerMock with JUnit. The coverage tool used is ECLEmma. With EasyMock, it shows the coverage properly in green (as covered). However, for the code that is unit tested with PowerMock, the coverage is shown in red (uncovered). Have read similar questions on the web. However, just wanted to check if there is a solution for this.
Thanks
Venkatesh
Yes, there is a solution for this:
First you will have to add this maven dependency:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule-agent</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
Then, instead of using this annotation #RunWith(PowerMockRunner.class), just add a #Rule in the Test class like this:
public class Test {
#Rule
public PowerMockRule rule = new PowerMockRule();
you can find more in this blog Make EclEmma test coverage work with PowerMock
It's a known problem : https://github.com/jayway/powermock/issues/422
And it has been for a long time, it won't be fixed anytime soon.
I suggest you use eCobertura instead.
This has worked in most cases in my project:
#Rule
public PowerMockRule rule = new PowerMockRule();
static {
PowerMockAgent.initializeIfNeeded();
}
Remove/Comment #RunWith(PowerMockRunner.class) & include following imports after adding powermock-module-javaagent-1.6.5.jar in your classpath:
import org.junit.Rule;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.powermock.modules.agent.PowerMockAgent;
Now right click->Coverage As->Coverage Configurations and add following lines in Arguments:
-ea -noverify -javaagent:path/to/powermock-module-javaagent-1.6.5.jar
Click Apply->Coverage.
Also note that #Before would not work in this case so you have to add all the stuffs in the methods marked with #Test from the method marked with #Before.
We have a static classes to mock. With mocking static classes, eclEmma code coverage plugin is not working in Eclipse. So what we did is, so placed #RunWith(JUnit4.class) (Instead of #RunWith(PowerMockRunner.class) ) before class and placed following lines inside class
static {
PowerMockAgent.initializeIfNeeded();
}
#Rule
public PowerMockRule rule = new PowerMockRule();
Compiled the class and ran the test class. Code coverage is working for class. This change is only in Eclipse IDE.
After writing test cases, we reverted code back to normal. Placed #RunWith(PowerMockRunner.class) instead of #RunWith(JUnit4.class) and commented above static code and powermockrule lines.
I have managed to generate PowerMock coverage with Jacoco, using powermock-module-javaagent.
Just make sure you put powermock agent after jacoco agent:
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>true</useSystemClassLoader>
<argLine>${jacocoArgLine} -javaagent:${settings.localRepository}/org/powermock/powermock-module-javaagent/${powermock.version}/powermock-module-javaagent-${powermock.version}.jar -noverify</argLine>
...
If you want to see an example, take a look at this project: https://github.com/jfcorugedo/sonar-scanner
Here you can see that sonar takes into account static methods and new statements mocked by PowerMock:
If you want to mock newstatements make sure you use PowerMockRule instead of PowerMockRunner.
Take a look at this test
Updating powermock version fix my issue below is maven dependency of supported version
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule-agent</artifactId>
<version>1.7.3</version>
<scope>test</scope>
</dependency>
Hope this helps !!!
I was facing the same issue. So, I updated the powerMockito version. Now I am using Power mock version 1.7.4 and Jacoco version 0.8.5. It's even working on eclipse also.
Here is some more detailed answer with full class.
couple of points to note:
I had to use spy instead of mockStatic
I had to move #PrepareForTest to method level.
As someone mentioned in other answers I also had to add following dependency
org.powermock:powermock-module-junit4-rule-agent:2.0.9
Below is my full class code for reference:
import java.util.ArrayList;
import java.util.Arrays;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.agent.PowerMockAgent;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.opensource.dummy.config.DummyConfig;
import org.opensource.dummy.data.builder.DataBuilder;
import org.opensource.dummy.model.SampleRequest;
import org.opensource.dummy.model.SampleType;
import org.opensource.dummy.model.Item;
import org.opensource.dummy.model.RequestType;
#RunWith(JUnit4.class)
public class MockedDataSearchServiceHelperTest {
static {
PowerMockAgent.initializeIfNeeded();
}
#Rule
public PowerMockRule rule = new PowerMockRule();
#PrepareForTest({ DummyConfig.class })
#SuppressWarnings("unchecked")
#Test
public void testSearchResponseValid() throws Exception {
Item Item = DataBuilder.createItem("2024-01-24", "2024-12-25", "61ef8faebec3bb72fbcf336d", null);
SampleRequest sampleRequest = DataBuilder.createSampleRequest(Arrays.asList(Item),
DataBuilder.createDeliveryMetrics(1), null, RequestType.ITEM);
BoolQueryBuilder boolQueryBuilder = DataSearchServiceHelper.createCustomQuery(sampleRequest, Item);
SearchRequestBuilder searchRequestBuilder = PowerMockito.mock(SearchRequestBuilder.class);
ActionFuture<SearchResponse> actionFuture = PowerMockito.mock(ActionFuture.class);
Client client = PowerMockito.mock(Client.class);
PowerMockito.spy(DummyConfig.class);
PowerMockito.doReturn(client).when(DummyConfig.class, "getClient");
Mockito.when(client
.prepareSearch(new String[] { "dummy" }))
.thenReturn(searchRequestBuilder);
Mockito.when(searchRequestBuilder.setIndicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN))
.thenReturn(searchRequestBuilder);
Mockito.when(searchRequestBuilder.setQuery(Mockito.any(QueryBuilder.class))).thenReturn(searchRequestBuilder);
Mockito.when(searchRequestBuilder.execute()).thenReturn(actionFuture);
Mockito.when(actionFuture.actionGet()).thenReturn(new SearchResponse(null, null, 0, 0, 0, 0, null, null));
SearchResponse searchResponse = DataSearchServiceHelper.getSearchResponse(
new String[] { "dummy" },
boolQueryBuilder, DataBuilder.createDeliveryMetrics(1), RequestType.ITEM);
Assert.assertNotNull(searchResponse);
}
}
I hope this helps to someone.
For mocking static classes, using #RunWith(PowerMockRunner.class) and running the "Coverage As JUnit Test" on Eclipse does show covered code as uncovered and it clearly does seem like an issue.
To add to the solutions above, in a maven project, you can try this..
In the root pom.xml, for report generation, add html as a format in cobertura-maven-plugin. Below is the way it looks.
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<configuration>
<formats>
<format>html</format>
<format>xml</format>
</formats>
</configuration>
</plugin>
Then, go to the module where your class resides and open target/site/cobertura/index.html file in Eclipse Web Browser or in the one of your choice. You can find the coverage information there.

PowerMockito: got InvalidUseOfMatchersException when use matchers mocking static method

When I'm testing this static method
public class SomeClass {
public static long someMethod(Map map, String string, Long l, Log log) {
...
}
}
with
import org.apache.commons.logging.Log;
#RunWith(PowerMockRunner.class)
//#PrepareForTest(SomeClass.class)
public class Tests {
#Test
public void test() {
...
PowerMockito.mockStatic(SomeClass.class);
Mockito.when(SomeClass.someMethod(anyMap(), anyString(), anyLong(), isA(Log.class))).thenReturn(1L);
...
}
}
I got InvalidUseOfMatchersException. My questions are:
Why I got this exception when all the arguments are using matchers? How to solve it? I have debugged it, found the isA(Log.class) returns null.
When I add the #PrepareForTest annotation to the test class and run the test, the junit makes no response. Why?
EDIT
I tried not to use argument matchers, and got
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
you stub either of: final/private/equals()/hashCode() methods.
Those methods cannot be stubbed/verified.
inside when() you don't call method on mock but on some other object.
at ...
So it seems due to the someMethod itself. There are synchronized block in the method. I'm wondering if Powermockito can mock that kind of method or not.
Try replacing the isA() to another any() call like this
Mockito.when(SomeClass.someMethod(anyMap(), anyString(), anyLong(), any(Log.class))).thenReturn(1L);
[EDIT]
Check your stacktrace when you get the exception. Are you seeing any NoClassDefFoundError reported? I noticed when I hadn't included the javassist.jar in my project I got a similar error to you.
I use PowerMockito and these are the jars I added to a brand new project to run the code that #Tom posted
powermock-mockito-1.4.10-full.jar
mockito-all-1.8.5.jar
javassist-3.15.0-GA.jar
junit-4.8.2.jar
common-logging-1.1.1.jar
Always a good idea to check that you're using compatible JAR versions, and also check if there are any other conflicting JARs in your projects classpath.
Better late than never, the line below:
Mockito.when(SomeClass.someMethod(anyMap(), anyString(), anyLong(),
isA(Log.class))).thenReturn(1L);
should be:
PowerMockito.when(SomeClass.someMethod(anyMap(), anyString(), anyLong(),
isA(Log.class))).thenReturn(1L);
So, instead of invoking Mockito.when, one should invoke PowerMockito.when
isA will always return null. This is by design, it is documented in the Javadoc for the isA() method. The reason for this is that null will always match the parameterized return type of class, which will always match the type of the argument in the stubbed method for which the isA() Matcher is used. The null value which is returned is not actually acted upon.
Seems to work fine for me. My complete test case:
import static org.mockito.Matchers.*;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SimpleLog;
import org.junit.Test;
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;
class SomeClass {
public static long someMethod(final Map map, final String string, final Long l, final Log log) {
return 2L;
}
}
#RunWith(PowerMockRunner.class)
#PrepareForTest(SomeClass.class)
public class InvalidUseOfMatchersTest {
#Test
public void test() {
// Mock the SomeClass' static methods, stub someMethod() to return 1
PowerMockito.mockStatic(SomeClass.class);
Mockito.when(SomeClass.someMethod(anyMap(), anyString(), anyLong(), isA(Log.class))).thenReturn(1L);
// null NOT is-a Log, uses default stubbing: returns 0
System.out.println(SomeClass.someMethod(null, null, 5L, null));
// SimpleLog passes is-a test, uses stubbed result: returns 1
System.out.println(SomeClass.someMethod(null, null, 7L, new SimpleLog("simplelog")));
}
}
Perhaps post a complete example to help diagnose what's going on?
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
I hope your project is using maven. Try including these jars to the build.

How to test code dependent on environment variables using JUnit?

I have a piece of Java code which uses an environment variable and the behaviour of the code depends on the value of this variable. I would like to test this code with different values of the environment variable. How can I do this in JUnit?
I've seen some ways to set environment variables in Java in general, but I'm more interested in unit testing aspect of it, especially considering that tests shouldn't interfere with each other.
The library System Lambda has a method withEnvironmentVariable for setting environment variables.
import static com.github.stefanbirkner.systemlambda.SystemLambda.*;
public void EnvironmentVariablesTest {
#Test
public void setEnvironmentVariable() {
String value = withEnvironmentVariable("name", "value")
.execute(() -> System.getenv("name"));
assertEquals("value", value);
}
}
For Java 5 to 7 the library System Rules has a JUnit rule called EnvironmentVariables.
import org.junit.contrib.java.lang.system.EnvironmentVariables;
public class EnvironmentVariablesTest {
#Rule
public final EnvironmentVariables environmentVariables
= new EnvironmentVariables();
#Test
public void setEnvironmentVariable() {
environmentVariables.set("name", "value");
assertEquals("value", System.getenv("name"));
}
}
Full disclosure: I'm the author of both libraries.
The usual solution is to create a class which manages the access to this environmental variable, which you can then mock in your test class.
public class Environment {
public String getVariable() {
return System.getenv(); // or whatever
}
}
public class ServiceTest {
private static class MockEnvironment {
public String getVariable() {
return "foobar";
}
}
#Test public void testService() {
service.doSomething(new MockEnvironment());
}
}
The class under test then gets the environment variable using the Environment class, not directly from System.getenv().
In a similar situation like this where I had to write Test Case which is dependent on Environment Variable, I tried following:
I went for System Rules as suggested by Stefan Birkner. Its use was simple. But sooner than later, I found the behavior erratic. In one run, it works, in the very next run it fails. I investigated and found that System Rules work well with JUnit 4 or higher version. But in my cases, I was using some Jars which were dependent on JUnit 3. So I skipped System Rules. More on it you can find here #Rule annotation doesn't work while using TestSuite in JUnit.
Next I tried to create Environment Variable through Process Builder class provided by Java. Here through Java Code we can create an environment variable, but you need to know the process or program name which I did not. Also it creates environment variable for child process, not for the main process.
I wasted a day using the above two approaches, but of no avail. Then Maven came to my rescue. We can set Environment Variables or System Properties through Maven POM file which I think best way to do Unit Testing for Maven based project. Below is the entry I made in POM file.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<PropertyName1>PropertyValue1</PropertyName1>
<PropertyName2>PropertyValue2</PropertyName2>
</systemPropertyVariables>
<environmentVariables>
<EnvironmentVariable1>EnvironmentVariableValue1</EnvironmentVariable1>
<EnvironmentVariable2>EnvironmentVariableValue2</EnvironmentVariable2>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
After this change, I ran Test Cases again and suddenly all worked as expected. For reader's information, I explored this approach in Maven 3.x, so I have no idea on Maven 2.x.
I think the cleanest way to do this is with Mockito.spy(). It's a bit more lightweight than creating a separate class to mock and pass around.
Move your environment variable fetching to another method:
#VisibleForTesting
String getEnvironmentVariable(String envVar) {
return System.getenv(envVar);
}
Now in your unit test do this:
#Test
public void test() {
ClassToTest classToTest = new ClassToTest();
ClassToTest classToTestSpy = Mockito.spy(classToTest);
Mockito.when(classToTestSpy.getEnvironmentVariable("key")).thenReturn("value");
// Now test the method that uses getEnvironmentVariable
assertEquals("changedvalue", classToTestSpy.methodToTest());
}
For JUnit 4 users, System Lambda as suggested by Stefan Birkner is a great fit.
In case you are using JUnit 5, there is the JUnit Pioneer extension pack. It comes with #ClearEnvironmentVariable and #SetEnvironmentVariable. From the docs:
The #ClearEnvironmentVariable and #SetEnvironmentVariable annotations can be used to clear, respectively, set the values of environment variables for a test execution. Both annotations work on the test method and class level, are repeatable as well as combinable. After the annotated method has been executed, the variables mentioned in the annotation will be restored to their original value or will be cleared if they didn't have one before. Other environment variables that are changed during the test, are not restored.
Example:
#Test
#ClearEnvironmentVariable(key = "SOME_VARIABLE")
#SetEnvironmentVariable(key = "ANOTHER_VARIABLE", value = "new value")
void test() {
assertNull(System.getenv("SOME_VARIABLE"));
assertEquals("new value", System.getenv("ANOTHER_VARIABLE"));
}
I don't think this has been mentioned yet, but you could also use Powermockito:
Given:
package com.foo.service.impl;
public class FooServiceImpl {
public void doSomeFooStuff() {
System.getenv("FOO_VAR_1");
System.getenv("FOO_VAR_2");
System.getenv("FOO_VAR_3");
// Do the other Foo stuff
}
}
You could do the following:
package com.foo.service.impl;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.verifyStatic;
import org.junit.Beforea;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
#RunWith(PowerMockRunner.class)
#PrepareForTest(FooServiceImpl.class)
public class FooServiceImpTest {
#InjectMocks
private FooServiceImpl service;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockStatic(System.class); // Powermock can mock static and private methods
when(System.getenv("FOO_VAR_1")).thenReturn("test-foo-var-1");
when(System.getenv("FOO_VAR_2")).thenReturn("test-foo-var-2");
when(System.getenv("FOO_VAR_3")).thenReturn("test-foo-var-3");
}
#Test
public void testSomeFooStuff() {
// Test
service.doSomeFooStuff();
verifyStatic();
System.getenv("FOO_VAR_1");
verifyStatic();
System.getenv("FOO_VAR_2");
verifyStatic();
System.getenv("FOO_VAR_3");
}
}
Decouple the Java code from the Environment variable providing a more abstract variable reader that you realize with an EnvironmentVariableReader your code to test reads from.
Then in your test you can give an different implementation of the variable reader that provides your test values.
Dependency injection can help in this.
This answer to the question How do I set environment variables from Java? provides a way to alter the (unmodifiable) Map in System.getenv(). So while it doesn't REALLY change the value of the OS environment variable, it can be used for unit testing as it does change what System.getenv will return.
Even though I think this answer is the best for Maven projects, It can be achieved via reflect as well (tested in Java 8):
public class TestClass {
private static final Map<String, String> DEFAULTS = new HashMap<>(System.getenv());
private static Map<String, String> envMap;
#Test
public void aTest() {
assertEquals("6", System.getenv("NUMBER_OF_PROCESSORS"));
System.getenv().put("NUMBER_OF_PROCESSORS", "155");
assertEquals("155", System.getenv("NUMBER_OF_PROCESSORS"));
}
#Test
public void anotherTest() {
assertEquals("6", System.getenv("NUMBER_OF_PROCESSORS"));
System.getenv().put("NUMBER_OF_PROCESSORS", "77");
assertEquals("77", System.getenv("NUMBER_OF_PROCESSORS"));
}
/*
* Restore default variables for each test
*/
#BeforeEach
public void initEnvMap() {
envMap.clear();
envMap.putAll(DEFAULTS);
}
#BeforeAll
public static void accessFields() throws Exception {
envMap = new HashMap<>();
Class<?> clazz = Class.forName("java.lang.ProcessEnvironment");
Field theCaseInsensitiveEnvironmentField = clazz.getDeclaredField("theCaseInsensitiveEnvironment");
Field theUnmodifiableEnvironmentField = clazz.getDeclaredField("theUnmodifiableEnvironment");
removeStaticFinalAndSetValue(theCaseInsensitiveEnvironmentField, envMap);
removeStaticFinalAndSetValue(theUnmodifiableEnvironmentField, envMap);
}
private static void removeStaticFinalAndSetValue(Field field, Object value) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, value);
}
}
Hope the issue is resolved. I just thought to tell my solution.
Map<String, String> env = System.getenv();
new MockUp<System>() {
#Mock
public String getenv(String name)
{
if (name.equalsIgnoreCase( "OUR_OWN_VARIABLE" )) {
return "true";
}
return env.get(name);
}
};
You can use Powermock for mocking the call. Like:
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.getenv("MyEnvVariable")).thenReturn("DesiredValue");
You can also mock all the calls with:
PowerMockito.mockStatic(System.class);
PowerMockito.when(System.getenv(Mockito.anyString())).thenReturn(envVariable);
The library https://github.com/webcompere/system-stubs/tree/master/system-stubs-jupiter - a fork of system-lambda - provides a JUnit 5 plug-in:
#ExtendWith(SystemStubsExtension.class)
class SomeTest {
#SystemStub
private EnvironmentVariables environmentVariables =
new EnvironmentVariables("name", "value");
#Test
void someTest() {
// environment is set here
// can set a new value into the environment too
environmentVariables.set("other", "value");
// tidy up happens at end of this test
}
}
The https://junit-pioneer.org/ alternative requires environment variable values to be known at compile time. The above also supports the setting
of environment variables in the #BeforeAll, which means it interoperates well with things like Testcontainers that might set up some resources needed by child tests.
A lot of focus in the suggestions above on inventing ways in runtime to pass in variables, set them and clear them and so on..? But to test things 'structurally', I guess you want to have different test suites for different scenarios? Pretty much like when you want to run your 'heavier' integration test builds, whereas in most cases you just want to skip them. But then you don't try and 'invent ways to set stuff in runtime', rather you just tell maven what you want? It used to be a lot of work telling maven to run specific tests via profiles and such, if you google around people would suggest doing it via springboot (but if you haven't dragged in the springboot monstrum into your project, it seems a horrendous footprint for 'just running JUnits', right?). Or else it would imply loads of more or less inconvenient POM XML juggling which is also tiresome and, let's just say it, 'a nineties move', as inconvenient as still insisting on making 'spring beans out of XML', showing off your ultimate 600 line logback.xml or whatnot...?
Nowadays, you can just use Junit 5 (this example is for maven, more details can be found here JUnit 5 User Guide 5)
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.7.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
and then
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
and then in your favourite utility lib create a simple nifty annotation class such as
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#EnabledIfEnvironmentVariable(named = "MAVEN_CMD_LINE_ARGS", matches = "(.*)integration-testing(.*)")
public #interface IntegrationTest {}
so then whenever your cmdline options contain -Pintegration-testing for instance, then and only then will your #IntegrationTest annotated test-class/method fire. Or, if you don't want to use (and setup) a specific maven profile but rather just pass in 'trigger' system properties by means of
mvn <cmds> -DmySystemPop=mySystemPropValue
and adjust your annotation interface to trigger on that (yes, there is also a #EnabledIfSystemProperty). Or making sure your shell is set up to contain 'whatever you need' or, as is suggested above, actually going through 'the pain' adding system env via your POM XML.
Having your code internally in runtime fiddle with env or mocking env, setting it up and then possibly 'clearing' runtime env to change itself during execution just seems like a bad, perhaps even dangerous, approach - it's easy to imagine someone will always sooner or later make a 'hidden' internal mistake that will go unnoticed for a while, just to arise suddenly and bite you hard in production later..? You usually prefer an approach entailing that 'given input' gives 'expected output', something that is easy to grasp and maintain over time, your fellow coders will just see it 'immediately'.
Well long 'answer' or maybe rather just an opinion on why you'd prefer this approach (yes, at first I just read the heading for this question and went ahead to answer that, ie 'How to test code dependent on environment variables using JUnit').
One slow, dependable, old-school method that always works in every operating system with every language (and even between languages) is to write the "system/environment" data you need to a temporary text file, read it when you need it, and then erase it. Of course, if you're running in parallel, then you need unique names for the file, and if you're putting sensitive information in it, then you need to encrypt it.
Simply
Add below maven dependency
<!-- for JUnit 4 -->
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-junit4</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
<!-- for JUnit 5 -->
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-jupiter</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
Inside your test, you can use something similar:
#Rule
public EnvironmentVariablesRule environmentVariablesRule = new EnvironmentVariablesRule();
#Test
public void givenEnvironmentCanBeModified_whenSetEnvironment_thenItIsSet() {
// mock that the system contains an environment variable "ENV_VAR" having value "value1"
environmentVariablesRule.set("ENV_VAR", "value1");
assertThat(System.getenv("ENV_VAR")).isEqualTo("value1");
}
Reference for more details
https://www.baeldung.com/java-system-stubs
You can try to dependent your code also from properties:
public static String host() {
return firstNonBlank(getenv("HOST"), getProperty("host"), "localhost");
}
So, in tests you can easily just add system property And your production code will precede using environment variables:
System.setProperty("HOST", "127.0.0.0");
Neat and clean approach to use mocking of Environment variables in Unit Testing is with the help of #SystemStub which comes as part of below dependency
testImplementation 'uk.org.webcompere:system-stubs-jupiter:2.0.1'
Below changes are on Junit 5 setup
Add below on your class
#ExtendWith(SystemStubsExtension.class)
and now use
#SystemStub
private EnvironmentVariables environmentVariables;
now you can mock the behavior of the environment variables by setting up the required key/value in your test.
For e.g. environmentVariables.set("MY_ENV_VARIABLE", "MY_REQUIRED_VALUE");
and this works perfectly fine, if your code is using environment variables
System.getenv().getOrDefault("MY_ENV_VARIABLE", "false");
Please note, it won't mock System.getProperties() it works only for System.getenv()
Well you can use the setup() method to declare the different values of your env. variables in constants. Then use these constants in the tests methods used to test the different scenario.
I use System.getEnv() to get the map and I keep as a field, so I can mock it:
public class AAA {
Map<String, String> environmentVars;
public String readEnvironmentVar(String varName) {
if (environmentVars==null) environmentVars = System.getenv();
return environmentVars.get(varName);
}
}
public class AAATest {
#Test
public void test() {
aaa.environmentVars = new HashMap<String,String>();
aaa.environmentVars.put("NAME", "value");
assertEquals("value",aaa.readEnvironmentVar("NAME"));
}
}
If you want to retrieve informations about the environment variable in Java, you can call the method : System.getenv();. As the properties, this method returns a Map containing the variable names as keys and the variable values as the map values. Here is an example :
import java.util.Map;
public class EnvMap {
public static void main (String[] args) {
Map<String, String> env = System.getenv();
for (String envName : env.keySet()) {
System.out.format("%s=%s%n", envName, env.get(envName));
}
}
}
The method getEnv() can also takes an argument. For instance :
String myvalue = System.getEnv("MY_VARIABLE");
For testing, I would do something like this :
public class Environment {
public static String getVariable(String variable) {
return System.getenv(variable);
}
#Test
public class EnvVariableTest {
#Test testVariable1(){
String value = Environment.getVariable("MY_VARIABLE1");
doSometest(value);
}
#Test testVariable2(){
String value2 = Environment.getVariable("MY_VARIABLE2");
doSometest(value);
}
}

Categories

Resources