How to set environment variable or system property in spring tests? - java

I'd like to write some tests that check the XML Spring configuration of a deployed WAR. Unfortunately some beans require that some environment variables or system properties are set. How can I set an environment variable before the spring beans are initialized when using the convenient test style with #ContextConfiguration?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:whereever/context.xml")
public class TestWarSpringContext { ... }
If I configure the application context with annotations, I don't see a hook where I can do something before the spring context is initialized.

You can initialize the System property in a static initializer:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:whereever/context.xml")
public class TestWarSpringContext {
static {
System.setProperty("myproperty", "foo");
}
}
The static initializer code will be executed before the spring application context is initialized.

The right way to do this, starting with Spring 4.1, is to use a #TestPropertySource annotation.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:whereever/context.xml")
#TestPropertySource(properties = {"myproperty = foo"})
public class TestWarSpringContext {
...
}
See #TestPropertySource in the Spring docs and Javadocs.

One can also use a test ApplicationContextInitializer to initialize a system property:
public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>
{
#Override
public void initialize(ConfigurableApplicationContext applicationContext)
{
System.setProperty("myproperty", "value");
}
}
and then configure it on the test class in addition to the Spring context config file locations:
#ContextConfiguration(initializers = TestApplicationContextInitializer.class, locations = "classpath:whereever/context.xml", ...)
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeTest
{
...
}
This way code duplication can be avoided if a certain system property should be set for all the unit tests.

All of the answers here currently only talk about the system properties which are different from the environment variables that are more complex to set, esp. for tests. Thankfully, below class can be used for that and the class docs has good examples
EnvironmentVariables.html
A quick example from the docs, modified to work with #SpringBootTest
#SpringBootTest
public class EnvironmentVariablesTest {
#ClassRule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables().set("name", "value");
#Test
public void test() {
assertEquals("value", System.getenv("name"));
}
}

If you want your variables to be valid for all tests, you can have an application.properties file in your test resources directory (by default: src/test/resources) which will look something like this:
MYPROPERTY=foo
This will then be loaded and used unless you have definitions via #TestPropertySource or a similar method - the exact order in which properties are loaded can be found in the Spring documentation chapter 24. Externalized Configuration.

For springboot, here would be the simplest way to do it in my opinion use the #SpringBootTest annotation you can in java:
#SpringBootTest(
properties = { "spring.application.name=example", "ENV_VARIABLE=secret" }
)
public class ApplicationTest {
// Write your tests here
}
Or in kotlin you can do:
#SpringBootTest(
properties = ["spring.application.name=example", "ENV_VARIABLE=secret"]
)
internal class ApplicationKTest {
// Write your tests here
}
And that's it your test should run overriding the properties with the one you have define in the annotation.
Let's say you had an application.yml looking like that:
spring:
application:
name: "app"
db:
username: "user"
password: ${ENV_VARIABLE:default}
Then during the test it would be:
The spring property spring.application.name will return the value "example"
The environment variable ENV_VARIABLE will return "secret", so if you use the value db.password in your code it would return "secret".

You can set the System properties as VM arguments.
If your project is a maven project then you can execute following command while running the test class:
mvn test -Dapp.url="https://stackoverflow.com"
Test class:
public class AppTest {
#Test
public void testUrl() {
System.out.println(System.getProperty("app.url"));
}
}
If you want to run individual test class or method in eclipse then :
1) Go to Run -> Run Configuration
2) On left side select your Test class under the Junit section.
3) do the following :

For Unit Tests, the System variable is not instantiated yet when I do "mvn clean install" because there is no server running the application. So in order to set the System properties, I need to do it in pom.xml. Like so:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<systemPropertyVariables>
<propertyName>propertyValue</propertyName>
<MY_ENV_VAR>newValue</MY_ENV_VAR>
<ENV_TARGET>olqa</ENV_TARGET>
<buildDirectory>${project.build.directory}</buildDirectory>
</systemPropertyVariables>
</configuration>
</plugin>

If you have a lot of test classes (IT tests that startup tomcat/server), and the tests are failing, you need to set the system property using
System.setProperty("ccm.configs.dir", configPath);
Since you need to make sure that is set before spring starts, you need to put it in a static context in a class.
And to make sure any test that may depend on it gets this set system property, define a simple config class in your test folder setting up that variable. P.S in my case the env variable that was needed was "ccm.configs.dir"
Here is what i added in my test folder,
#Configuration
public class ConfigLoader {
static {
System.setProperty("ccm.configs.dir", "path/to/the/resource");
}
}
And all my integration test classes were able to get that variable already set by the time they are run.

#Jimmy Praet's modified answer for JUnit5:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(locations = "classpath:whereever/context.xml")
public class TestWarSpringContext {
#BeforeAll
static void initAll() {
System.setProperty("myproperty", "foo");
}
#AfterAll
static void tearDownAll() {
System.clearProperty("myproperty");
}
...
}

All the answers are grouped in two:
pass the env variables one by one using some annotation or System.setProperty;
to have another application.properties *yml
Both approaches work but based in several projects, to maintain 2 properties files and/or to pass all the properties in the string properties = {"myproperty = foo"} on each test will be a complicated
This worked for me:
Keep just one application.properties
Use env variables in the application.properties
truestore.custom.location = ${JRE_CACERT_LOCATION}
truestore.custom.password = ${JRE_CACERT_PASSWORD}
In the test that requires the application.properties like #WebMvcTest(FooController.class) and at the same time the env vars declares on application.properties add this code:
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import com.jayway.jsonpath.JsonPath;
#WebMvcTest(HealthController.class)
public class HealthControllerTest {
static {
File resourcesDirectory = new File("src/test/resources");
try (Stream<String> stream = Files.lines(
Paths.get(resourcesDirectory.getAbsolutePath() + File.separator + "application.env"))) {
stream.forEach(rawLine -> {
String pair[] = rawLine.trim().split("=");
System.setProperty(pair[0], pair[1]);
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
create just one file with env variables required for all the tests: /src/test/resources/application.env
JRE_CACERT_LOCATION=/foo/bar/jre/cacert
JRE_CACERT_PASSWORD=changeme
Dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath />
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
Advantages
devops compatible
run on every shell
that file could be used for the developer to configure his Eclipse or Intellij
useful to configure manually the application in the server or with some configuration manager

Related

Not able to assign a value from command-line to a property in application.properties file

I have the following settings in my spring-boot project
application.properties:
file_path=${lookup_path}
lookup-file.file_1=${file_path}/file1.lku
lookup-file.file_2=${file_path}/file2.lku
and a corresponding config class LookupFileConfiguration, and also handler class to use this configuration:
#Component
#ConfigurationProperties("lookup-file")
public class LookupFileConfiguration {
private String file_1;
private String file_2;
// getter and setter methods skipped here
}
#Component
public class MyHandler {
#Autowired
private LookupFileConfiguration files;
}
I tried to run from command line:
mvn test -Drun.arguments=--lookup_path=C:\\my-proj\target\test-classes\lookupFiles\\
But it is not working. lookup_path seems not be used in the application.properties.
If I hard-code in application.properties file
file_path=C:\\my-proj\target\test-classes\lookupFiles\\
it is working fine.
I know I can use profiles for different environments. However, my project will run test cases in Bamboo plan. So I can only use bamboo variable ${bamboo_build_working_directory} but cannot predict the directory for test. Please help with a proper solution/suggestion for such case.

java.lang.Exception: No runnable methods exception in running JUnits

I am trying to run the JUnit on my Linux command prompt /opt/junit/ contains the necessary JARS(hamcrest-core-1.3.jar and junit.jar) and class files and I am using the following command to run the JUnit:
java -cp hamcrest-core-1.3.jar:junit.jar:. org.junit.runner.JUnitCore TestRunner
TestJunit class:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class TestJunit {
#Test
public void testAdd() {
String str= "Junit is working fine";
assertEquals("Junit is working fine",str);
}
}
TestRunner:
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
public class TestRunner {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(TestJunit.class);
for (Failure failure : result.getFailures()) {
System.out.println("fail ho gaya"+failure.toString());
}
System.out.println("passed:"+result.wasSuccessful());
}
}
I am getting the following exception on running this
JUnit version 4.11
.E
Time: 0.003
There was 1 failure:
1) initializationError(TestRunner)
java.lang.Exception: No runnable methods
at org.junit.runners.BlockJUnit4ClassRunner.validateInstanceMethods(BlockJUnit4ClassRunner.java:169)
at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:104)
at org.junit.runners.ParentRunner.validate(ParentRunner.java:355)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:57)
at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runner.Computer.getRunner(Computer.java:40)
at org.junit.runner.Computer$1.runnerForClass(Computer.java:31)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:101)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:87)
at org.junit.runners.Suite.<init>(Suite.java:80)
at org.junit.runner.Computer.getSuite(Computer.java:28)
at org.junit.runner.Request.classes(Request.java:75)
at org.junit.runner.JUnitCore.run(JUnitCore.java:117)
at org.junit.runner.JUnitCore.runMain(JUnitCore.java:96)
at org.junit.runner.JUnitCore.runMainAndExit(JUnitCore.java:47)
at org.junit.runner.JUnitCore.main(JUnitCore.java:40)
FAILURES!!!
Tests run: 1, Failures: 1
In my case I had wrong package imported:
import org.testng.annotations.Test;
instead of
import org.junit.Test;
Beware of your ide autocomplete.
You will get this exception, if you use the JUnit 4.4 core runner to execute a class that has no "#Test" method.
Kindly consult the link for more info.
courtesy vipin8169
My controller test in big shortcut:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TaskControllerTest {
//...
//tests
//
}
I just removed "public" and magically it worked.
This solution will apply to a very small percentage of people, typically people implementing their own JUnit test runners and using a separate ClassLoader.
This can happen when you load a class from a different ClassLoader, then attempt to run that test from an instance of JUnitCore loaded from the system class loader. Example:
// Load class
URLClassLoader cl = new URLClassLoader(myTestUrls, null);
Class<?>[] testCls = cl.loadClass("com.gubby.MyTest");
// Run test
JUnitCore junit = new JUnitCore();
junit.run(testCls); // Throws java.lang.Exception: No runnable methods
Looking at the stack trace:
java.lang.Exception: No runnable methods
at org.junit.runners.BlockJUnit4ClassRunner.validateInstanceMethods(BlockJUnit4ClassRunner.java:169)
at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:104)
at org.junit.runners.ParentRunner.validate(ParentRunner.java:355)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:57)
at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
at org.junit.runner.JUnitCore.run(JUnitCore.java:138)
The problem actually occurs at BlockJUnit4ClassRunner:169 (assuming JUnit 4.11):
https://github.com/junit-team/junit/blob/r4.11/src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java#L95
Where it checks which methods are annotated with #Test:
protected List<FrameworkMethod> computeTestMethods() {
return getTestClass().getAnnotatedMethods(Test.class);
}
In this case, Test.class will have been loaded with the system ClassLoader (i.e. the one that loaded JUnitCore), therefore technically none of your test methods will have been annotated with that annotation.
Solution is to load JUnitCore in the same ClassLoader as the tests themselves.
Edit: In answer to the question from user3486675, you need to create a ClassLoader that doesn't delegate to the system class loader, e.g.:
private static final class IsolatedURLClassLoader extends URLClassLoader {
private IsolatedURLClassLoader(URL[] urls) {
// Prevent delegation to the system class loader.
super(urls, null);
}
}
Pass this a set of URLs that includes everything you need. You can create this by filtering the system classpath. Note that you cannot simply delegate to the parent ClassLoader, because those classes would then get loaded by that rather than the ClassLoader of your test classes.
Then you need to kick off the whole JUnit job from a class loaded by this ClassLoader. It gets messy here. Something like this utter filth below:
public static final class ClassLoaderIsolatedTestRunner {
public ClassLoaderIsolatedTestRunner() {
// Disallow construction at all from wrong ClassLoader
ensureLoadedInIsolatedClassLoader(this);
}
// Do not rename.
public void run_invokedReflectively(List<String> testClasses) throws BuildException {
// Make sure we are not accidentally working in the system CL
ensureLoadedInIsolatedClassLoader(this);
// Load classes
Class<?>[] classes = new Class<?>[testClasses.size()];
for (int i=0; i<testClasses.size(); i++) {
String test = testClasses.get(i);
try {
classes[i] = Class.forName(test);
} catch (ClassNotFoundException e) {
String msg = "Unable to find class file for test ["+test+"]. Make sure all " +
"tests sources are either included in this test target via a 'src' " +
"declaration.";
throw new BuildException(msg, e);
}
}
// Run
JUnitCore junit = new JUnitCore();
ensureLoadedInIsolatedClassLoader(junit);
junit.addListener(...);
junit.run(classes);
}
private static void ensureLoadedInIsolatedClassLoader(Object o) {
String objectClassLoader = o.getClass().getClassLoader().getClass().getName();
// NB: Can't do instanceof here because they are not instances of each other.
if (!objectClassLoader.equals(IsolatedURLClassLoader.class.getName())) {
throw new IllegalStateException(String.format(
"Instance of %s not loaded by a IsolatedURLClassLoader (loaded by %s)",
cls, objectClassLoader));
}
}
}
THEN, you need to invoke the runner via reflection:
Class<?> runnerClass = isolatedClassLoader.loadClass(ClassLoaderIsolatedTestRunner.class.getName());
// Invoke via reflection (List.class is OK because it just uses the string form of it)
Object runner = runnerClass.newInstance();
Method method = runner.getClass().getMethod("run_invokedReflectively", List.class);
method.invoke(...);
I had the same problem now with testing code. That was caused in spring boot because of the #RunWith annotation. I have used:
#RunWith(SpringRunner.class)
With that annotation there is JUnit Vintage running which can't find any tests and gives you the error. I have removed that and only JUnit Jupiter is running and everything is fine.
I had to change the import statement:
import org.junit.jupiter.api.Test;
to
import org.junit.Test;
In my case, I was using the wrong Test import. The correct one was import org.junit.Test;
If you are using import org.junit.jupiter.api.Test (Junit 5)
and #RunWith(SpringRunner.class), SpringRunner is on Junit4, junit gets confused.
Removing public before class name will work as
Junit 5 complains about public test classes.
From Docs:
JUnit5 is more tolerant regarding the visibilities of Test classes than JUnit4, which required everything to be public.
In this context, JUnit5 test classes can have any visibility but private, however, it is recommended to use the default package visibility, which improves readability of code.
For me, replacing import org.junit.jupiter.api.Test; with import org.junit.Test; helped.
in my case i just disabled
//#RunWith(SpringRunner.class)
and there is no exception
I also faced this issue and failed to figure out the reason for the same for sometimes.
Later i found that auto import issue using IDE. That is imports of the program.
Basically i was using eclipse IDE. And I was importing a wrong class "org.junit.jupiter.api.Test" into the program instead of required class "org.junit.Test". Hence check your imports before running any programs.
You can also get this if you mix org.junit and org.junit.jupiter annotations inadvertently.
I had similar issue/error while running JunitCore along side with Junit Jupiter(Junit5) JUnitCore.runClasses(classes); after removing #RunWith(SpringRunner.class) and
ran with #SpringBootTest #FixMethodOrder(MethodSorters.NAME_ASCENDING) i am able to resolve the issue for my tests as said in the above comments.
https://stackoverflow.com/a/59563970/13542839
A bit of heuristic/experience here, I am running a Spring Boot project, and I was getting JUnit Jupiter tests appearing alongside JUnit Vintage. The JUnit Vintage ones were failing, when I removed the public access modifier the Junit Vintage tests disappeared, as a result achieving the behaviour I wanted.
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles(profiles = {"test"})
public class TestSuiteName {
||
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles(profiles = {"test"})
class TestSuiteName {
Why were JUnit Jupiter and JUnit Vintage separated When I Running TestCase in IntelliJ?
I got this error because I didn't create my own test suite correctly:
Here is how I did it correctly:
Put this in Foobar.java:
public class Foobar{
public int getfifteen(){
return 15;
}
}
Put this in FoobarTest.java:
import static org.junit.Assert.*;
import junit.framework.JUnit4TestAdapter;
import org.junit.Test;
public class FoobarTest {
#Test
public void mytest() {
Foobar f = new Foobar();
assert(15==f.getfifteen());
}
public static junit.framework.Test suite(){
return new JUnit4TestAdapter(FoobarTest.class);
}
}
Download junit4-4.8.2.jar I used the one from here:
http://www.java2s.com/Code/Jar/j/Downloadjunit4jar.htm
Compile it:
javac -cp .:./libs/junit4-4.8.2.jar Foobar.java FoobarTest.java
Run it:
el#failbox /home/el $ java -cp .:./libs/* org.junit.runner.JUnitCore FoobarTest
JUnit version 4.8.2
.
Time: 0.009
OK (1 test)
One test passed.
If you're running test Suite via #RunWith(Suite.class) #Suite.SuiteClasses({}) check if all provided classes are really test classes ;).
In my case one of the classes was an actual implementation, not a test class. Just a silly typo.
if the class annotated with #RunWith(SpringRunner.class) But we class doesn't contain any test methods then we will face this issue.
Solution: if we make to abstract we will not get this or if remove public then also we will not face this issue.
In Eclipse, I had to use New > Other > JUnit > Junit Test. A Java class created with the exact same text gave me the error, perhaps because it was using JUnit 3.x.
The simplest solution is to add #Test annotated method to class where initialisation exception is present.
In our project we have main class with initial settings. I've added #Test method and exception has disappeared.
I was able to fix by manually adding the junit jar to my project classpath. The easiest way I found to do this was by adding a /lib directory in the project root. Then i just put the junit.jar inside /lib and junit tests starting working for me.
I faced the same with my parent test setUp class which has annotation #RunWith(SpringRunner.class) and was being extended by other testClasses.
As there was not test in the setUpclass , and Junit was trying to find one due to annotation #RunWith(SpringRunner.class) ,it didn't find one and threw exception
No runnable methods exception in running JUnits
I made my parent class as abstract and it worked like a charm .
I took help from here https://stackoverflow.com/a/10699141/8029525 .
Thanks for help #froh42.
the solution is simple
if you importing
import org.junit.Test;
you have to run as junit 4
right click ->run as->Test config-> test runner-> as junit 4
For me I added JUnit4.12 and Hamcrest1.3 on the classpath and changed import org.testng.annotations.Test; or import org.testng.annotations.*; to import org.junit.Test;. It finally works fine!
If there is,take out of pom.xml
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
I got the same error when I missed to add access modifier public to this test-case-method, after added it works. I used JUnit 4. For Junit 5, same test-case works without access specifier to test-case-method.
Tried this and it worked with Junit5:
#SpringBootTest(classes = {ServletWebServerFactoryAutoConfiguration.class},
webEnvironment = RANDOM_PORT,
properties = {"spring.cloud.config.enabled=false"})
#ExtendWith(MockitoExtension.class)
#AutoConfigureMockMvc
I am going to add one more solution for those using Eclipse (and Gradle):
In my case I had a trivial test class such as this one:
package somepackage;
import static org.junit.Assert.assertFalse;
import org.junit.Test;
public class SomeTest
{
#Test
public void test_someClass_doesNotDoThing_whenCreated()
{
SomeClass someClass = new SomeClass();
assertFalse( "", someClass.doesThing() );
}
}
This checks all the relevant checkboxes:
Correct imports are used
#Test annotation is present
Test method is public
No different class loader
Still got the "No runnable methods" exception. Apparently Eclipse didn't get the memo which I suspect is prone to occurring when either the test project or some other project in the work space has compilation errors (irrelevant to the test class).
This was resolved by:
Calling "Refresh Gradle Project" in Eclipse for the entire workspace (possibly optional)
Calling "Project" -> "Clean" in Eclipse
This made Eclipse understand there was a valid test method in my test class.
If using jupiter, please remove #RunWith.
import org.junit.jupiter.api.Test;
import org.springframework.test.context.junit4.SpringRunner;
//#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
public class DepartmentServiceTests {
#Autowired
DepartmentService service;
#MockBean
DepartmentRepository repository;
#Test
public void findOneByIdTest(){
int id = 1;
Department expected = new Department(1,"401E","AAC01","DL","1");
when(repository.findOneById(id)).thenReturn(expected);
Department actual = service.findOneById(id);
assertEquals(expected, actual);
}
}

Maven: How can I run/include a class in a jar with the failsafe plugin?

I've made my own class with an #RunWith(AllTests.class) on it for integration tests that I want to execute, but I've put it in a reusable jar. I'm trying to tell failsafe to use it but I'm not sure how to because the documentation for includes says:
A list of elements specifying the tests (by pattern) that should be included in testing. When not specified and when the test parameter is not specified, the default includes will be
<includes>
<include>**/IT*.java</include>
<include>**/*IT.java</include>
<include>**/*ITCase.java</include>
</includes>
Each include item may also contain a comma-separated sublist of items, which will be treated as multiple entries.
This parameter is ignored if the TestNG suiteXmlFiles parameter is specified.
But this test runner is in the classpath, not on the filesystem. How can I tell failsafe to use my class runner and only my class runner?
Can't you use the #RunWith annotation provided by jUnit?
import com.example.YourTestRunner;
#RunWith(YourTestRunner.class)
public class SomeIntegrationTest {
#Test
public void simpleTest() {
// given, when, then
}
}
Update
Based on the comment:
My class isn't a test runner, but a class that uses #RunWith
you can use inheritance to solve the problem:
#RunWith(SomeTestRunner.class)
#Ignore("Not a real test class because it does not contain any #Test methods, but needed to keep surefire happy")
public class ParentTest {
// this is the reusable class that is in the jar file
}
public class SomeIntegrationTest extends ParentTest {
#Test
public void simpleTest() {
// given, when, then
}
}

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);
}
}

Grouping JUnit tests

Is there any way to group tests in JUnit, so that I can run only some groups?
Or is it possible to annotate some tests and then globally disable them?
I'm using JUnit 4, I can't use TestNG.
edit: #RunWith and #SuiteClasses works great. But is it possible to annotate like this only some tests in test class? Or do I have to annotate whole test class?
JUnit 4.8 supports grouping:
public interface SlowTests {}
public interface IntegrationTests extends SlowTests {}
public interface PerformanceTests extends SlowTests {}
And then...
public class AccountTest {
#Test
#Category(IntegrationTests.class)
public void thisTestWillTakeSomeTime() {
...
}
#Test
#Category(IntegrationTests.class)
public void thisTestWillTakeEvenLonger() {
...
}
#Test
public void thisOneIsRealFast() {
...
}
}
And lastly,
#RunWith(Categories.class)
#ExcludeCategory(SlowTests.class)
#SuiteClasses( { AccountTest.class, ClientTest.class })
public class UnitTestSuite {}
Taken from here: https://community.oracle.com/blogs/johnsmart/2010/04/25/grouping-tests-using-junit-categories-0
Also, Arquillian itself supports grouping:
https://github.com/weld/core/blob/master/tests-arquillian/src/test/java/org/jboss/weld/tests/Categories.java
Do you want to group tests inside a test class or do you want to group test classes? I am going to assume the latter.
It depends on how you are running your tests. If you run them by Maven, it is possible to specify exactly what tests you want to include. See the Maven surefire documentation for this.
More generally, though, what I do is that I have a tree of test suites. A test suite in JUnit 4 looks something like:
#RunWith(Suite.class)
#SuiteClasses({SomeUnitTest1.class, SomeUnitTest2.class})
public class UnitTestsSuite {
}
So, maybe I have a FunctionTestsSuite and a UnitTestsSuite, and then an AllTestsSuite which includes the other two. If you run them in Eclipse you get a very nice hierarchical view.
The problem with this approach is that it's kind of tedious if you want to slice tests in more than one different way. But it's still possible (you can for example have one set of suites that slice based on module, then another slicing on the type of test).
To handle the globally disabling them, JUnit (4.5+) has two ways One is to use the new method assumeThat. If you put that in the #BeforeClass (or the #Before) of a test class, and if the condition fails, it will ignore the test. In the condition you can put a system property or something else that can be globally set on or off.
The other alternative is to create a custom runner which understands the global property and delegates to the appropriate runner. This approach is a lot more brittle (since the JUnit4 internal runners are unstable and can be changed from release to release), but it has the advantage of being able to be inherited down a class hierarchy and be overridden in a subclass. It is also the only realistic way to do this if you have to support legacy JUnit38 classes.
Here is some code to do the custom Runner. Regarding what getAppropriateRunnerForClass might do, the way I implemented it was to have a separate annotation that tells the custom runner what to run with. The only alternative was some very brittle copy paste from the JUnit code.
private class CustomRunner implements Runner
private Runner runner;
public CustomRunner(Class<?> klass, RunnerBuilder builder) throws Throwable {
if (!isRunCustomTests()) {
runner = new IgnoredClassRunner(klass);
} else {
runner = getAppropriateRunnerForClass(klass, builder);
}
public Description getDescription() {
return runner.getDescription();
}
public void run(RunNotifier notifier) {
runner.run(notifier);
}
}
EDIT: The #RunWith tag only works for a whole class. One way to work around that limiation is to move the test methods into a static inner class and annotate that. That way you have the advantage of the annotation with the organization of the class. But, doing that won't help with any #Before or #BeforeClass tags, you will have to recreate those in the inner class. It can call the outer class's method, but it would have to have its own method as a hook.
In JUnit 5 you can declare #Tag for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4
From the javadoc :
tags are used to filter which tests are executed for a given test
plan. For example, a development team may tag tests with values such
as "fast", "slow", "ci-server", etc. and then supply a list of tags to
be used for the current test plan, potentially dependent on the
current environment.
For example you could declare a test class with a "slow" #Tag that will be inherited for all methods and override it for some methods if required :
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
#Tag("slow")
public class FooTest{
//
#Test
void loadManyThings(){
...
}
#Test
void loadManyManyThings(){
...
}
#Test
#Tag("fast")
void loadFewThings(){
...
}
}
You could apply the same logic for other test classes.
In this way test classes (and methods too) belongs to a specific tag.
As a good practice instead of copying and pasting #Tag("fast") and #Tag("slow") throughout the test classes, you can create custom composed annotations.
For example :
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Tag;
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Tag("slow")
public #interface Slow {
}
and use it as :
#Test
#Slow
void slowProcessing(){
...
}
To enable or disable test marked with a specific tag during the text execution you can rely on the maven-surefire-plugin documentation :
To include tags or tag expressions, use groups.
To exclude tags or tag expressions, use either excludedGroups.
Just configure in your pom.xml the plugin according to your requirement (example of the doc) :
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<groups>acceptance | !feature-a</groups>
<excludedGroups>integration, regression</excludedGroups>
</configuration>
</plugin>
</plugins>
</build>
For information the test goal documentation is not updated.
Try JUnit Test Groups. From documentation :
#TestGroup("integration")
public class MyIntegrationTest {
#ClassRule
public static TestGroupRule rule = new TestGroupRule();
...
}
Execute a simple test group: -Dtestgroup=integration
Execute multiple test groups: -Dtestgroup=group1,group2
Execute all test groups: -Dtestgroup=all
You can create test Suite objects that contain groups of tests. Alternatively, your IDE (like Eclipse) may have support for running all the tests contained in a given package.
You can Use Test Suite(http://qaautomated.blogspot.in/2016/09/junit-test-suits-and-test-execution.html) or you can Junit Categories(http://qaautomated.blogspot.in/2016/09/junit-categories.html) for grouping your test cases effectively.

Categories

Resources