This Tutorial will Discuss JUnit @DisplayName Annotation & Conditional Test Execution based on JRE Version, System Property, OS or Environment Variables:
In our previous tutorial, we learned how to create a nested class with @Nested annotation in JUnit 5. We also explored the workflow of nested class with lifecycle call back methods.
We got the know-how of the annotation @TestInstance. Besides, we also addressed some of the frequently asked questions related to nested classes.
In this tutorial, we will learn about giving a customized name to the tests.
=> Check Out The Perfect JUnit Training Guide Here.
Table of Contents:
- @DisplayName Annotation For Custom Name
- Disabling Tests
- Conditional Disabling Vs Unconditional Disabling
- @Disabled Annotation
- Disable Or Enable Tests Per Certain Logic Or If Condition
- Disable Or Enable Tests Based On Operating System
- Disable Or Enable Tests Based On Java Runtime Environment
- Disable Or Enable Tests Based On The Environment Variables
- Disable Or Enable Tests Based On System Property
- FAQs On Conditional Disabling Of Tests
- Conclusion
@DisplayName Annotation For Custom Name
In our earlier tutorials in this series, we used the annotation @DisplayName several times on the test method. However, we haven’t formally discussed it yet. Thus, we will brush up on the topic and reiterate the annotation and its usage along with its benefit in reporting.
Using the annotation @DisplayName on a test method or a class enables you to add a customized name to it and this name shows up on the report/result.
Why do we provide customized names to the tests?
- This makes the class name and the test name quite readable even to a person with no technical skills.
- Beyond this benefit, there is no logical addition to a class or test. This is supported by JUnit 5 only.
Let’s try understanding it with an example:
Here is the class named junitTest.java in which the @DisplayName annotation has been added on the top of the class as well as on top of the test. The Display name can include an emoji as well.
@DisplayName=” JUnit class for Calculation ?” class junitTest { public int a, b; a=10; b=10; @DisplayName=” Multiplication of two integers” @Test void calc () { assertEquals (100, (a*b),”has passed”); } }
After execution, the report shows up with the display names of the class and the test.
Disabling Tests
The term “Disabling Test” means to skip a test or test during execution.
Why do we need to disable certain tests? There could be certain test cases which must not be run.
There might be various reasons for this, the test may not be about certain code changes or the code for the test cases may be still under development so we avoid running them. In such cases, we need to disable them so that they do not execute.
Conditional Disabling Vs Unconditional Disabling
In this section, we will discuss the difference between the plain usage of @disabled over a test and the conditional disabling of tests.
The @Disabled annotation is used to disable a test for execution without providing a specific condition on it. It doesn’t look for any condition to satisfy before disabling it during execution.
The conditional disabling of the tests is the way using which the tests could be disabled based on certain conditions like the Java Runtime Environment (JRE) used, or the Operating system (OS) used, the user logged into the system, etc.
Similar to disabling tests based on certain conditions, we could also enable them based on the same set of conditions.
@Disabled Annotation
This was already covered in our earlier tutorial on “Skipping Test Execution”. However, let us quickly reiterate the topic briefly with a couple of examples of the snippet of code.
Class level
@Disabled annotation could be used at the class level or test level. When the annotation is used at the class level, all the tests under the class are skipped during execution.
Example:
@Disabled(“Testcases are not ready for execution”) class GmailLogin{ @Test void login(){ //the code for login goes here } @Test void compose(){ //the code for compose email goes here } }
Here, the two tests i.e. both login() and compose() are skipped during execution as the class itself is disabled. There is no specific condition to be satisfied to disable these tests login() and compose().
Testcase Level
When the annotation is used at the test level, that specific annotated test under the class is skipped during execution.
Example:
class GmailLogin{ @Test void login(){ //the code for login goes here } @Disabled(“Testcase is not ready for execution”) @Test void compose(){ //the code for compose email goes here } }
Here, the testcase compose() is skipped whereas the testcase login() runs successfully. There is no specific condition to be satisfied to disable a specific test or tests in a class.
Disable Or Enable Tests Per Certain Logic Or If Condition
There could be multiple conditions based on which we decide to enable or disable specific tests. You may use annotations @DisabledIf and @EnabledIf to handle skipping or enabling the tests per evaluation of a script logic.
The annotation @DisabledIf allows the user to set a specific condition over a test method to skip that test in case the given condition is verified to be true during execution.
Similarly using the annotation @EnabledIf allows the user to set a specific condition over a test method to allow the execution of the test if the verification of the condition passes.
If the annotation @DisabledIf or @EnabledIf is set on a class, then all the tests within the class are disabled or enabled, respectively during test execution after evaluation of the scripts.
Parameters For The Annotation:
Three input parameters go inside the annotation:
#1) Value: This is the mandatory parameter that is the logical condition or the actual script for verification based on which the test is skipped or executed. It is an array of String values.
#2) Engine: By default, the scripting engine is “Nashorn”. To override the value, this parameter can be set too. However, this is an optional parameter. It is a String value
#3) Reason: This is again an optional parameter. It is used to display messages as logger messages in case our test fails or passes. By default, the reason is set to
“Script ‘[script}’ evaluated to: {result}” where {result} is either ‘enabled’ or ‘disabled’ post evaluation of script passed in the parameter ‘value’.
Placeholders For ‘reason’ Parameter:
There are 3 placeholders supported for reason parameter:
#1) {annotation}: This represents the annotation that is used. If the annotation used is @DisabledIf, {annotation} returns @DisabledIf which is nothing but the String representation of the annotation.
#2) {script}: This is the script or the String array passed in the “value” parameter.
#3) {result}: This gives the return value after the evaluation of the script in the parameter “value”.
Example #1:
Here is a basic example of the usage of @EnabledIf to demonstrate an example with the script text set to the argument “value” implicitly.
The below code snippet imply that only the admin will be able to access and update permissions for a file stored on the shared folder so that the file becomes read-only for all the users. Hence, the test case getFileAccess() is enabled to be run only if the logged-in user is admin1.
Here, the reason parameter for the annotation is not explicitly set, hence it will display the result with the default pattern.
@Test @EnabledIf("admin1 == systemProperty.get('user.name')") void getFileAccess(){ // file access will be enabled for admin1 user login only File confidential_file = new File("C:\\Automation\Software\vendorTools.xlsx"); if(confidential_file.exists() == true) { //make file read only confidential_file.setExecutable(true); confidential_file.setReadable(true); confidential_file.setWritable(false); System.out.println("File changed to read only"); } }
The resultant after test execution: The testcase getFileAccess() is skipped if the username is other than “admin1” else it runs successfully.
The report tab shows as below:
The reason parameter was not explicitly set in the annotation. So, the default pattern of “Script ‘[script}’ evaluated to: {result}” displays.
Example #2:
Updating the same example with the reason parameter explicitly.
@Test @EnabledIf(value={"admin1 == systemProperty.get('user.name')"}, reason=”The annotation {annotation} results into {result} after evaluation of the script {script}”) void getFileAccess(){
The resultant after test execution: The testcase getFileAccess() is skipped if the username is other than “admin1” else it runs successfully.
The report tab shows as below:
The reason parameter was explicitly set in the annotation. So, the default pattern of the reason is overridden to show the user-defined pattern of display in the result.
Hence, the default pattern “Script ‘[script}’ evaluated to: {result}” is overridden to display in the pattern =” The annotation {annotation} results into {result} after the evaluation of the script {script}”
Script Bindings
Certain script bindings are used in the script for the ‘value’ parameter for the annotation @EnabledIf and @DisabledIf. The script bindings are always in a key-value pair which is nothing but a map structure.
The following two accessors access the property from the system:
systemEnvironment: It helps to fetch environment variables. This is the same as using the getenv() method from System class in the core Java. For instance, systemEnvironment.get(“USERNAME”) is equivalent to using System.getenv(‘USER’)
systemProperty: It helps to fetch property variables. This is the same as using the getProperty() method from the System class in the core Java. For instance, systemProperty.get(‘user.name’) is equivalent to using the System.getProperty(“user.name”)
Other script bindings could be used as shown below. These help to access the details from the test:
junitTags: This accesses the set of tags added on the test(s).
junitDisplayName: This gets the display name of the current test or the class on which the annotation @EnabledIf or @DisabledIf is added.
junitUniqueId: This accesses the unique id of the test
junitConfigurationParameter: This accesses the configuration parameter stored under specific key values. (E.g. testenv=”QA”)
Example #3:
This is an example to demonstrate script bindings in the annotation when there are multiple conditions to follow for @EnabledIf or @DisabledIf
Here, the scenario is that the ‘BankApp’ is the class for a banking application with multiple testcases to test the application. Testcases loginTest() and paymentTest() can be accessed and run by any resource irrespective of whether the resource is from the automation team or the functional team.
Let’s consider one testcase extractXMLtoExcel() which is logically implemented:
- To access a server location,
- Fetch the XML file and
- The code parses the xml file into an excel file and
- Stores it in a specific location on the system.
In this example, we will have certain assumptions and conditions to execute the testcase:
- Automation team resources have their username initiating with the word “automation”.
- The server from where the XML file can be accessed and fetched works only on Windows 10.
- Automation VMs initiate with the name “DESKTOP_CN5”.
- The condition to run this test would be that only resources from the automation team are required to run this test and on automation virtual machines only.
Thus, considering the condition from step 4 above, we have added the below script in our example in the annotation @EnabledIf
- java.net.InetAddress.getLocalHost().getHostName() fetches the computername that is verified to contain ‘DESKTOP_CN5’ as per assumption in step 3 and above.
- systemProperty.get(‘user.name’) is verified to initiate with ‘automation’ as per the assumption in step 1 and above and
- systemEnvironment.get(“os.Name”) is verified to check if the system has Windows 10 as per the assumption in step 2.
class BankApp(){ @Test void loginTest(){ } @Test void paymentTest(){ } @Test @EnabledIf(value={ “var computerName=java.net.InetAddress.getLocalHost().getHostName()”, “systemProperty.get(‘user.name’).contains(‘automation’)” + “&& systemEnvironment.get (“os.Name”).equalsIgnoreCase(‘Windows 10’)” + “&& computerName.contains(‘DESKTOP_CN5’)” }) void extractXMLtoExcel(){ //parsing an xmL file from a server location to an Excel file } }
Disable Or Enable Tests Based On Operating System
There could be scenarios wherein we need to enable or disable the execution of our test cases on a specific operating system. There could be various reasons to do so.
It could be an explicit requirement from a client or a restriction to meet certain security agreements or maybe certain limitations of the application under test to run or not run on the specific operating system(s).
The annotation @DisabledOnOs is used with the annotation @Test to disable the test for a specific operating system. Similarly, @EnabledOnOs annotation is used to enable the test when there is a specific operating system.
The parameters for Annotations and Placeholders For Reason Parameter also apply for the annotations @DisabledOnOs and @EnabledOnOs.
Here is an example where the testcase notforWindowsOS() is skipped for execution when it is run on the Windows operating system. This should run for any other operating system.
The testcase runOnlyForLinux() is enabled for Linux only. So, the test should not run on any other operating system other than Linux.
@Test @DisabledOnOs(value={OS.WINDOWS}) void notforWindowsOS(){ //…. } @Test @EnabledOnOs(value={OS.LINUX}) void runOnlyForLinux(){ //…. }
This same code snippet can be written using @EnabledIf and @DisabledIf as shown below:
@Test @DisabledIf(value={“systemProperty.get(“os.name”). contains(‘WINDOWS’)”}) void notforWindowsOS(){ //…. } @Test @EnabledOnOs(value={“systemProperty.get(“os.name”). contains(‘LINUX)”) void runOnlyForLinux(){ //…. }
Disable Or Enable Tests Based On Java Runtime Environment
There could be scenarios wherein we need to enable or disable the execution of our test cases based on the Java runtime environment version. This is nothing but returns the same value as that of System.getProperty(“java.version’).
The annotation @DisabledOnJre is used with the annotation @Test to disable the test for a specific java version on the system. Similarly, @EnabledOnJre annotation is used to enable the test when there is a specific java version on the system.
The parameters for Annotations and Placeholders For Reason Parameter also applies for the annotations @DisabledOnJre and @EnabledOnJre
The String text, set for the parameter ‘value’ for the annotation is in the format JRE.JAVA_<version number> E.g. JRE.JAVA_10
Here is an example where the testcase skipOnJava12() is skipped for execution when it is run on a system having Java 12. This should run for any other version of Java.
The testcase runOnlyOnJava12and10() is enabled on the system with Java 12 and Java 10 only. So, the test should not run on systems with Java versions other than 10 and 12.
@Test @DisabledOnJre(value={JRE.JAVA_12}) void skipOnJava12(){ //…. } @Test @EnabledOnJre(value={JRE.JAVA_12, JRE.JAVA_10}) void runOnlyOnJava12and10(){ //…. }
This same code snippet can be written using @EnabledIf and @DisabledIf as shown below.
@Test @DisabledIf(systemProperty.get(‘java.version’).startsWith(’12.’)) void skipOnJava12(){ //…. } @Test @EnabledIf(systemProperty.get(‘java.version’).startsWith(’10.’)&& systemProperty.get(‘java.version’).startsWith(’12.’)) void runOnlyOnJava12and10(){ //…. }
Disable Or Enable Tests Based On The Environment Variables
A testcase can be disabled or enabled per the evaluation of the script verifying a specific environment variable, too.
@DisabledIfEnvironmentVariable and @EnabledIfEnvironmentVariable are the annotations for disabling and enabling the tests based on the match against a specific environment variable, respectively.
The parameters and placeholders above are not the same here for these annotations.
Then, let us look at the parameters for these annotations.
Parameters For The Annotations:
named – The name of the environment variable to retrieve. This is a mandatory parameter for the annotations. This works equivalent to the method System.getenv().
matches – It includes the regular expression against which the environment variable is matched. This is a mandatory parameter for the annotations. This is equivalent to String.matches(regular expression).
There is no placeholder for these annotations.
Here is the example where skipTestIfSauceLabs() is skipped during execution if the Selenium service used is Saucelabs and runTestOnlyonSaucery3() runs only if the Selenium service on the system is Saucery3.
@Test- @DisabledIfEnvironmentVariable(named=”SELENIUM_SERVICE” , matches=”saucelabs”) void skipTestIfSaucelabs(){ //…. } @Test @EnabledIfEnvironmentVariable(named=” SELENIUM_SERVICE” , matches=”saucery3”) void runTestOnlyonSaucery3(){ //…. }
Disable Or Enable Tests Based On System Property
A testcase can be disabled or enabled per the evaluation of the script verifying any system Property.
@DisabledIfSystemProperty and @EnabledIfSystemProperty are the annotations for disabling and enabling the tests based on the match against a specific system property value, respectively.
The parameters and placeholders are the same as above for these annotations.
Here is an example where the testcase skipTestIfJava1.8() skips if the java version is 1.8.18 and testcase runTestOnlyonJava2() runs only if the java version 2.0.1
@Test @DisabledIfSystemProperty(named=”java.version” , matches=”1.8.18”) void skipTestIfJava1.8(){ //…. } @Test @EnabledIfSystemProperty (named=”java.version” , matches=”2.0.1”) void runTestOnlyonJava2() { //…. }
These annotations @DisabledIfSystemProperty and @EnabledIfSystemProperty can be alternatively used in the place of annotations @DisabledIf and @EnabledIf
@DisabledSystemProperty and @EnabledSystemProperty with named=” os.name” can also be used instead of the operating system-based annotation.
@DisabledSystemProperty and @EnabledSystemProperty with named=”java. version” can also be used instead of JRE based annotation.
FAQs On Conditional Disabling Of Tests
Q #1) If a test in a class was annotated with @DisabledIfSystemProperty and another test annotated with @EnabledIf, then, how do I fetch the name of the Methods and the annotations used on each method for a class?
Answer: This is a very valid question and worth answering. We shall rephrase the question to make it more generic as this question could apply for any other annotation(s) within a class.
Irrespective of JUnit 4 or 5 used, we can still fetch the name of the methods and the annotations added on each method in the class.
There are multiple ways to do this:
- Using Java methods like method.getName and method.getDeclaredAnnotations();
- Using methods from the Spring framework.
As of now, we will learn how to use Java methods to achieve the results.
Here is an example of a JUnit 4 program ‘JUnitTestcase.java’ with three methods – setUp(), junitMethod1() and getAllMethodAnnotations().
The method setUp() is annotated with @Before
The method junitMethod1() is annotated with @Test and @Category
The method getAllMethodAnnotations() is the prominent method that will do the job of getting all the methods in the class along with the list of annotations added on each of it. We annotate this method with @Deprecated and @Test.
The code would be as follows:
package demo.tests; import java.util.*; import java.lang.String; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; public class JUnitTestCase1 { @Before public void setUp() { } @Category(UnitTest.class) @Test public void junitMethod1() { } @Test @Deprecated public void getAllMethodAnnotations() throws InterruptedException { JUnitTestCase1 c = new JUnitTestCase1(); Method [] methods = c.getClass().getMethods(); Method method = null; for (Method m : methods) { if ((m.getName().equals("getAllMethodAnnotations")) || (m.getName().equals("junitMethod1"))||(m.getName().equals("setUp"))) { method = m; System.out.println("Annotations added for the Method name: " + m.getName()+"()"); Annotation[] annotation = method.getDeclaredAnnotations(); for (Annotation annote : annotation) { Annotation myAnnotations = (Annotation) annote; //change the display format to @<annotation> if(myAnnotations.annotationType().toString().equalsIgnoreCase("interface org.junit.Test")) { System.out.println("@Test"); } if(myAnnotations.annotationType().toString().equalsIgnoreCase("interface java.lang.Deprecated")) { System.out.println("@Deprecated"); } if(myAnnotations.annotationType().toString().equalsIgnoreCase("interface org.junit.experimental.categories.Category")) { System.out.println("@Category"); } if(myAnnotations.annotationType().toString().equalsIgnoreCase("interface org.junit.Before")) { System.out.println("@Before"); } } System.out.println(); } } } }
Explanation of the Code:
#1) We can get the array of methods using the class.getMethods().
#2) We loop through all the methods to verify if the method name are the methods setUp(), junitMethod1() and getAllMethodAnnotations().
When the array of methods would fetch all the methods in the class, we included an additional statement of ‘if condition’ to verify the method names.
The method.getName() is a Java function and not a JUnit function and it returns all the methods used in the class. In case you delete the if condition of verifying the method names, then the console window shows the list of all the below methods in the class returned by method.getName().
The below screenshot of the console window includes the all Java methods in the class including the JUnit methods:
We hope this clears the confusion of why we had to use additional if condition to verify the method names.
#3) The method.getDeclaredAnnotations() gets the Array of Annotations for the method.
#4) We loop through all the annotations in the class.
#5) <AnnotationInstance>.annotationType() returns the interface name of the annotation used on the method.
#6) We use additional if statements to convert the display format of the annotation interfaces to @Annotation.
Post execution of the code, the consoled window will be as shown below:
Annotations added for the Method name: getAllMethodAnnotations() @Test @Deprecated Annotations added for the Method name: junitMethod1() @Category @Test Annotations added for the Method name: setUp() @Before
Through the same code, the Methods with annotation @DisabledIfSystemProperty and @EnabledIf can also be fetched.
Q #2) How do we get the count of disabled testcases in a class?
Answer: Here, is the code to count the disabled testcases in a class. The logic implemented here is based on the number of Disabled annotations used in a class.
@Disabled @Test public void test_JUnit1() { } @Disabled @Test public void test_JUnit2() { } //Java version used is Java 4 @DisabledOnJre(value={JRE.JAVA_12}) @Test public void test_JUnit3() { } @Test public void test_JUnit4() { } @Test public void getDisabledTestCount() throws InterruptedException { JUnitTestCase c = new JUnitTestCase(); Method[] methods = c.getClass().getMethods(); Method method = null; int count=0; for (Method m : methods) { if((m.getName().equals("getDisabledTestCount"))||(m.getName().contains("test_JUnit"))) { method = m; Annotation[] annotation = method.getDeclaredAnnotations(); for (Annotation annote : annotation) { Annotation myAnnotations = (Annotation) annote; if(myAnnotations.annotationType().toString().equalsIgnoreCase("interface org.junit.jupiter.api.Disabled")||myAnnotations.annotationType().toString().contains("interface org.junit.jupiter.api.condition.Disabled”)) { count++; }}}} System.out.println("Count of @Disabled testcases"+count); }
The console window shows the below:
Count of @Disabled testcases: 3
The method test_JUnit3() is annotated with DisabledOnJRE when the Java Runtime Environment (JRE) version is 12. This method does not skip but executes as we assumed that the system has Java Runtime Environment (JRE) version 4 and not 12.
Yet the above code includes this method in the count of the disabled tests as we fetch the count of the Disabled annotations. If in case, we wish to dynamically get the exact count of the methods skipped during execution, then we may have to implement the logic based on the Test result that we get post-execution.
This is just an example, we will cover the rest when we put forth a tutorial on the implementation of different JUnit classes like TestCase, TestSuite, and TestResult and their respective methods.
Conclusion
In this tutorial, we learned about @DisplayName annotation and also extensively explored conditional test execution based on the Java Runtime Environment (JRE) version, or system property, or on an operating system or environmental variables based on the scripting logic.
We learned about the parameters of the annotations and also addressed certain questions and discussed how to fetch the annotation on the methods in the class.
=> Visit Here To Learn JUnit From Scratch.