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);
}
}
Related
I am trying to create a unit test, however, whenever I run it I encounter null values on my variables. Debugging the issue points to my environment variables not being used globally on my unit test. As a class I'm testing calls another class that then references its values from an environment variable. How do I make use of global environment variables in IntelliJ?.
I've already added Environment Variables in the Test Configuration like this,
However, this doesn't seem to be used on the entire project. Hence classes calling this environment variables are returning null. How do I make my Unit Test and the classes it calls to use the globally declared environment variables?. TIA. Below is a snippet of my code of how my environment variables are being used in a class
#Value("${fintech.clientid}")
private String clientId;
#Value("${fintech.secret}")
private String clientSecret;
#Value("${digifi-host}")
private String fintechHost;
#Value("${fintech-digifi-login-endpoint}")
private String loginUrl;
#Value("${fintech-single-session-endpoint}")
private String singleSessionUrl;
What this does is from the Class, it calls the values stored in my application.properties file. And from the application.properties file, it tries to look for the proper values in the environment variables declared on run time.
So from Java Class > application.properties file/config > Runtime Environment Variables
Below is the screenshot of variables with null values when debugging the test. As you can see, all of the values are null which means it didn't load the environment variables I have put in the Unit Test. On the other test case where I had a temporary fix (as I put in the answer here), they are populated and hence loading the environment variables properly, but in my many other test cases like this one, it doesn't.
PS:
I've already found related articles in stackoverflow, but they are all test-class specific and that uses surefire plugins or setting the environment variables or via pom, but I don't need it to be there as it is a requirement for us to use environment variables on runtime as the values of the variables should be hidden and not visible on the code. I just simply need the entire project to use a global environment variable when it is doing its Unit Test. Much like how my project would use the environment variables I set in the IDE in normal runtime.
Just for Reference. I already did the ff.:
A.
#ClassRule
public final static EnvironmentVariables environmentVariables = new EnvironmentVariables().set("property1", "value1");
B.
#ContextConfiguration(locations = "classpath:application.properties")
public class LoginServiceTest {
...
}
C.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<SERVER_PORT>8082</SERVER_PORT>
</environmentVariables>
</configuration>
</plugin>
D.
#TestPropertySource(properties = {"property1=value1","property2=value2",})
public class LoginServiceTest {
...
}
E.
public class LoginServiceTest {
static{
System.setProperty("property1", "value1");
System.setProperty("property2", "value2");
...
}
UPDATE:
This solution worked on all of my test. You can refer here for more details.
#RunWith(SpringRunner.class) //Add this
#SpringBootTest(classes = Application.class) //Add this
public class LoginServiceTest {
...
}
Then on
Run > Edit Configurations > Templates (in sidemenu) > JUnit
Then put all your environment variables here.
Click + on the Upper Left window & Apply.
Then delete all existing Unit Test command in your configuration and proceed to re-running all your test again plainly.
Then you'll see the environment variables being added automatically on all your new and upcoming unit test.
You won't have to manually add an environment variables to each unit test command alright as what I happen to do annoying before. Should you ever have an update on your environment variables. Delete the main unit test configuration you need to have an update, and update the JUnit Template instead so that changes will reflect to new unit tests that you may have.
PS:
Thing is, I still encountered the very same issue on some of my Unit
Test, that even though the environment variables are being read
properly on runtime and in some unit test alright with the above configurations. It is only during
the other many Unit Test the very same environment variables are not
being retrieved or received properly by the classes. At first I
thought my classes are not really getting the proper environment
variables and there must be something wrong as to how I stored the
environment variables in IntelliJ, which it did not really, but it
turns out this code format I have is the issue.
#Value("${fintech.clientid}")
private String clientId;
#Value("${fintech.secret}")
private String clientSecret;
#Value("${digifi-host}")
private String digifiHost;
It turns out,even though though this particular code works on runtime, it wont on Unit Test time, like ever.
Hence I tinkered with this particular codes until I was already able to get the proper environment variables on Unit Test. Refer to the changes from the code above to the one that fixed the issue for me below.
#Value("${fintech.clientid}")
public void getClientId(String tempClientId) {
clientId = tempClientId;
}
private String clientId;
#Value("${fintech.secret}")
public void getClientSecret(String tempClientSecret) {
clientSecret=tempClientSecret;
}
private String clientSecret;
#Value("${digifi-host}")
public void getDigifiHost(String tempDigifiHost) {
digifiHost=tempDigifiHost;
}
private String digifiHost;
I need to use some environment variables in my unit tests. Consider the following test class
public class MyTest
{
private String myVar=null;
#Before
public void setUp()
{
myVar = System.getEnv("myEnv");
}
#After
public void tearDown() {}
#Test
public void myTestMethod()
{
assertNotNull(myVar);
}
}
now in the eclispe debug/run settings of the MyTest class, i define the environment variable as
myEnv=myVal
and when I run the MyTest class as a jUnit test, the myTestMethod passes.
However, when i try to run myTestMethod as a jUnit test, it gives me a NullPointerException.
The only way to make it pass is to create a new run/debug configuration specifically for myTestMethod and creating the environment variable again in the new configuration.
This is extremely frustrating as I can have dozens of environment variables and tests.
Is there any way to solve this problem in eclipse? I have not worked with intelliJ but does that also suffer from the same problem? Or is it more of a jUnit issue?
Existing launch configuration are not reused when running a single test method: see Eclipse bug 213316, except in the JUnit view. Perhaps there are plug-ins that implement the behavior you want. Alternatively, you could implement the behavior you want yourself, based on the Eclipse source code.
No solution, but a shortening: Press Ctrl and click on the run button or on a run configuration menu item to open the previous launched or the corresponding launch configuration.
You may also take into consideration to change your code to solve the problem of at runtime unchangeable environment variables:
In production code use Env.getenv(...) instead of System.getenv(...):public class Env {
private static Function connector = System::getenv;
public static void setConnector(Function newConnector) {
connector = newConnector;
}
public static String getenv(String name) {
return connector.apply(name);
}
}
In test code set environment variables by redirect Env.getenv(...):#Before
public void setUp() {
Map env = new HashMap<>();
env.put("myEnv", "42");
Env.setConnector(env::get);
//...
}
I've tried to avoid duplicate code in JUnit test, but I'm kind of stuck.
This is my first test, for the second one it has exactly the same methods but different service (different input). instead of the TestCaseResourceTest1 I have TestCaseResourceTest2. Now what could be the proper way to test both? I want to have a separate file for test number 2, how should I avoid the duplicate code? (ex. use the beforeFileTest() method)
public class TestCaseResourceTest1 {
#Mock
private TestService testService;
#Mock
private AreaService areaService;
private TestCaseService1 testCaseService1; // is changed in test2
#Before
public void before() throws Exception{
testCaseService1 = mock(TestCaseService1.class); // is changed in test2
MockitoAnnotations.initMocks(this);
beforeFileTest();
}
private void beforeFileTest() throws Exception{
doReturn(true).when(areaService).chechExists(any(String.class), eq(false));
}
#Test
public void verifyFileExists() throws Exception{
verifyOtherArea(testCaseService1); // is changed in test2
doReturn(false).when(areaService).chechExists(any(String.class), eq(false));
}
}
just lines with comment is changed in test2 are differences.
Tnx
Given this excerpt from your question:
… instead of the TestCaseResourceTest1 I have TestCaseResourceTest2 … I want to have a separate file for test number 2
… the standard ways of sharing code between test cases are:
Create a Test Suite and include the shared code in the test suite (typically in #BeforeClass and #AfterClass methods). This allows you to (1) run setup code once (per suite invocation); (2) encapsulate shared setup/teardown code and (3) easily add more tests cases later. For example:
#RunWith(Suite.class)
#Suite.SuiteClasses({
TestCaseResourceTest1.class,
TestCaseResourceTest2.class
)}
public class TestSuiteClass {
#BeforeClass
public void setup() {
beforeFileTest();
}
private void beforeFileTest() throws Exception {
// ...
}
}
Create an abstract class which parents TestCaseResourceTest1 and TestCaseResourceTest2 and let those test cases call the shared code in the parent (typically via super() calls). With this approach you can declare default shared code in the parent while still allowing sub classes to (1) have their own behaviour and (2) selectively override the parent/default behaviour
Create a custom JUnit runner, define the shared behaviour in this runner and then annotate the relevant test cases with #RunWith(YourCustomRunner.class). More details on this approach here
Just to reiterate what some of the other posters have said; this is not a common first step so you may prefer to start simple and only move to suites or abstract classes or custom runners if your usage provides a compelling reason to do so.
I had the such situation and it was a sign about wrong implementation design. We are talking about pure unit tests where we test exactly what is implemented in the production classes. If we need duplicated tests it means we probably have duplication in implementation.
How did I resolve it in my project?
Extracted common logic into parent service class and implemented unit tests for it.
For child services I implemented tests only for particular implemented code there. No more.
Implemented an integration tests on real environment were both services were involved and tested completely.
Assuming you want to have the exact same test run for 2 different classes (and not mocking it as in your example code), you can create an abstract test class, that has abstract method that returns an instance of the class to be tested.
Something in the vein of:
public abstract class TestCaseResourceTest {
protected abstract TestCaseService1 getServiceToTest();
#Before
public void before() throws Exception {
testCaseService1 = getServiceToTest();
MockitoAnnotations.initMocks(this);
beforeFileTest();
}
#Test
public void test() {
// do your test here
}
}
public class ConcreteTest extends TestCaseResourceTest {
protected TestCaseService1 getServiceToTest() {
return new TestCaseService();
}
}
public class ConcreteTest2 extends TestCaseResourceTest {
protected TestCaseService1 getServiceToTest() {
return new DifferentService();
}
}
Have you considered using JUnit 5 with its http://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests ?
It allows you to re-use your tests with different input. This is an example from the documentation which illustrates what you can do now with JUnit 5:
#ParameterizedTest
#ValueSource(strings = { "Hello", "World" })
void testWithStringParameter(String argument) {
assertNotNull(argument);
}
But you can also create your methods which return the input data:
#ParameterizedTest
#MethodSource("stringProvider")
void testWithSimpleMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("foo", "bar");
}
Here I am using just strings, but you can really use any objects.
If you are using Maven, you can add these dependencies to start using JUnit 5:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.0.0-RC2</version>
<scope>test</scope>
</dependency>
The only annoying thing about JUnit 5 is that it is not released yet.
When going from one test to two tests, you don't know what will be duplicate code, so I find it useful to put everything into one test method. In this case, start by putting the contents of the #Before and beforeFileTest methods inline in the test.
Then you can see that it is just te service that needs changing, so you can extract everything except that into a helper method that is called from two tests.
Also, after you have two tests that are calling the same helper method and are happy with that test coverage, you could look into writing parameterized tests. For example with JunitParams: https://github.com/Pragmatists/junitparams/wiki/Quickstart
I am trying to setup TestNG so that it gives me new instances of my class variable for each test (basically like JUnit). I need this as I intend to parallelize my tests at the method level. I have been experimenting with both standalone Guice and the built in Guice functionality that TestNG provides to try to accomplish this but I have had no luck. I know that I can use ThreadLocal, but calling .get() for every variable in the test is pretty unappealing. I am weary of using GuiceBerry as it does not really have a lot of updates/activity and it's last release is not even acquirable via Maven. I am pretty set on TestNG as for all the inconvenience this is causing me it still does a lot of great things. I am open to things other tools though to accomplish my goal. Basically I want things setup so the below tests would work consistently. Any help would be greatly appreciated.
// just has a variable thats a class called child with a simple string variable
// with a value of "original
Parent p;
#Test
public void sometest1(){
p.child.value = "Altered";
Assert.assertTrue(p.child.value.equals("Altered"));
}
#Test
public void sometest2(){
Assert.assertTrue(p.child.value.equals("original"));
}
TestNG doesn't create a new instance for each test. If you want such a behavior than I recommend creating separate test classes. e.g.:
public class SomeTest1 {
Parent p;
#Test
public void something(){
p.child.value = "Altered";
Assert.assertTrue(p.child.value.equals("Altered"));
}
}
public class SomeTest2 {
Parent p;
#Test
public void something(){
Assert.assertTrue(p.child.value.equals("original"));
}
}
Note that TestNG can run JUnit 3 and JUnit 4 tests (you might maintain a mixed suite depending on the style you want to use in a given test class).
In a Junit test, is it possible to test for variables in the main method?
My main method something like this looks like this:
package edu.blah.class.project1;
public class Program {
public static void main(String[] args) {
try {
int result = Utility.analyze();
} catch (Exception e) {
System.out.println("Error");
}
}
}
Is it possible to obtain the variable result in a JUnit class? Is the only way to make it a public variable? Thanks!
In a Junit test, is it possible to test for variables in the main method?
Err ... no. Unless you have a JUnit test that calls the main method explicitly, then the main method won't be called in a unit test.
Besides, the variable here is result which is a local variable, and there is no way to reach into a method to test some local variable. (Java simply doesn't allow it ...)
Is it possible to obtain the variable result in a JUnit class?
In this example, result gets the value of the call Utility.analyze(). There is no reason why a JUnit test could not do that too ... and thereby get the same value. (Or at least it could in this example.)
Is the only way to make it a public variable?
No ... see above.
However, if you are actually trying to test the value of result in the context of the main method, then you are right. The only way to get hold of the value would be to expose it as a static variable. It doesn't necessarily have to be public static though. (Indeed, if you were prepared to write do some "nasty" reflection you could even get your JUnit test to extract and test the value of a private static field.)
However, I think you are taking the wrong approach top this. Rather than trying to figure out how to reach into a static method (or any method), it would be better if you restructured the code to make it more testable. For example:
package edu.blah.class.project1;
public class Program {
public int run(String args[]) {
return Utility.analyze();
}
public static void main(String[] args) {
try {
new Program().run(args);
} catch (Exception e) {
System.out.println("Error");
}
}
}
With a small amount of restructuring (like this), you can make it a lot easier to write unit tests for all of the important functionality. You are left with the "problem" it is still hard to unit test the main method. However, you have reduced the method to the point that it is so simple that you can verify it is correct by visual inspection: unit testing main is now unnecessary.
No, local variables are allocated on stack and are not available outside the method.
Your wishing to check the local variable from test means that you probably should split your method into several smaller ones. Generally this is one of the principals of TDD - Test Driven Development.
Unit tests can call any accessible method, including the main method. Apart from it being recognized as an application entry point there is nothing special about the main method.
Regarding how to test your example, in the current state the local variable result cannot be accessed. Before resorting to all sorts of tricks to access it, ask yourself the question why you would want to test for it?
A unit test should check the contract of a method, which comprises of the input parameters, the return value and any side effects. Testable side effects are:
changing the state of the encompassing class
calling instances of classes that are fields in the class under test
Hard to test side effects are:
static calls
direct interaction with the environment like the file system, network, threads
behavior depending on system time
In most cases the situations that are hard to test can be avoided by refactoring your application, making it more testable.
However, in your example none of the above applies. The local variable 'result' is not used for anything. So in the current state of your application you don't have to (and cannot) test for its value.
When I use my imagination to guess what your application could/should be doing an extended and testable version may look something like this:
public class Program {
public static final String ERROR_MESSAGE = "Error";
// Utility modeled as a dependency to avoid static access
private Utility utility;
// a poor man's logger, better to use a logging framework like log4j
private PrintStream logger;
// 'business logic' extracted into a separate method to be tested
public void execute(String[] args) {
try {
// static access to Utility replaced with instance access
// passing the args to make testing more interesting
int result = utility.analyze(args);
// added logging of the result to make testing more interesting
logger.println(result);
} catch (Exception e) {
// Static access to System.out replaced with logger instance
logger.println(ERROR_MESSAGE);
}
}
// setters used for dependency injection
public void setUtility(Utility utility) {
this.utility = utility;
}
public void setLogger(PrintStream logger) {
this.logger = logger;
}
// application entry point does basic initalization and depency injection
public static void main(String[] args) {
// create application instance
Program program = new Program();
// inject dependencies
program.setUtility(new Utility());
program.setLogger(System.out);
// call application
program.execute(args);
}
}
And the unit test, using JUnit4:
import static org.mockito.Mockito.*;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.*;
#RunWith(MockitoJUnitRunner.class)
public class ProgramTest {
// #InjectMocks creates an instance and injects any specified mocks
#InjectMocks
private Program instance;
// #Mock creates a mock instance, which can be used to specify behavior using when() and verify access using verify()
#Mock
private Utility utility;
#Mock
private PrintStream printStream;
// #Test indicates a test method in JUnit4
#Test
public void testExecuteHappy() {
// SETUP
String[] args = new String[];
int expectedResult = 42;
// specify behavior of the Utility mock to return the expected result
when(utility.analyze()).thenReturn(expectedResult);
// CALL
instance.execute(args);
// VERIFY
// check that utility.analyse() was called with the args
verify(utility).analyze(args);
// verify that logger.println() was called with the expected result
verify(logger).println(expectedResult);
}
#Test
public void testExecuteError() {
// SETUP
String[] args = new String[];
// specify behavior of the Utility mock to throw an exception
when(utility.analyze()).doThrow(new Exception("test exception));
// CALL
instance.execute(args);
// VERIFY
// check that utility.analyse() was called with the args
verify(utility).analyze(args);
// verify that logger.println() was called with the error message
verify(logger).println(Program.ERROR_MESSAGE);
}
}
Typically dependencies require additional configuration like which database to access, connection pools etc. In a larger application dependency injection would be done by a framework like Spring or CDI rather than the main method.
Here are the Maven dependencies needed for this:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
Add these to your pom.xml. If you don't use Maven, download these .jar files and add them to the class path used for compilation:
Junit 4.11
Mockito core 1.9.5
Note: I didn't compile or test the above code so there might be small typo's