This Tutorial Explains all about JUnit Annotations along with a Comparison of Annotations in JUnit 4 vs JUnit 5:
We learned the different ways to execute JUnit test cases and saw how to create and execute a test suite in our previous tutorial.
In this tutorial, we will get to know the prominent aspect of JUnit which is called Annotations. Starting from JUnit 4, annotations are in place and make JUnit very simple, more beneficial, and much more user-friendly to use.
Annotations is a Java API that enables JVM to recognize the type of the method defined in the test class. There are ‘lifecycles call back annotations’ that are frequently used.
=> Visit Here To Learn JUnit From Scratch.
Table of Contents:
JUnit Annotations – JUnit 4 vs JUnit 5
A test execution goes through different stages of the lifecycle as given below:
- Before starting a test, there are certain activities to be performed on the initiation of a class.
- Certain other activities to be performed before a testcase begins execution.
- Certain activities that need to be performed after execution of the test and
- Certain activities at the end of the execution of all the tests in a class.
In order to ascertain that these activities are performed throughout each stage of the Java lifecycle, there needs to be certain user-defined methods or functions in place termed as ‘lifecycle call-back methods.
The behavior of these lifecycle call-back methods is determined by the built-in ‘lifecycle call-back annotations’ used in JUnit.
Example: Let us try making it even simpler by relating these lifecycle call-back methods and annotations to an example of testing a coffee vending machine.
- A method machineReady() that checks if water, milk, and coffee beans are available before the machine is switched on might be needed.
- Another method startMachine() that switches the machine on, places a fresh new paper cup in the machine might be needed.
- A testcase that checks the ‘HotWater()’ option.
- Another testcase that checks the ‘Capuccino()’ option.
- Another testcase that checks the ‘ExpressoCoffee()’ option.
- Another method “throwCup()” that throws the used cups in the bin.
- A class-level method “throwTrashandSwitchOff()” throws overflowing waste liquid from the tray into the basin and switches off the machine.
So, in the above example, here is how the lifecycle of the test follows:
- startMachine() will run prior to each testcase – HotWater(), Capuccino() and ExpressoCoffee() runs.
- Each of this testcase also follows the method throwCup().
- The methods machineReady() and throwTrashandSwitchOff() are class-level method which run only once for a class. The method machineReady() runs once while the class initiates execution. The method throwTrashandSwitchOff() runs once after all the test cases complete execution.
Now, the question arises these are mere Java methods only, then:
- How will we insist JVM run machineReady() only once at the class level and throwTrashandSwitchOff() at the end of the class execution?
- How will we make JVM know that startMachine() and throwCup() needs to be run before running each testcase and after completion of each testcase execution, respectively?
- How can we make JVM identify that the methods HotWater(), Capuccino() and ExpressoCoffee() are test cases to be run?
Answer: The sole answer to the above questions is that the lifecycle callback annotations do all the required magic.
(For now, let us assume that we are creating this class in JUnit 4)
The lifecycle annotations – @BeforeClass, @AfterClass, @Before, @After, and @Test are the real answers to the above three questions. We are pretty sure, that after reading the below pointers, you will get clear with lifecycle call back annotations and its workflow.
- Annotate the method machineReady() with @BeforeClass and JVM will make it run once during the start of the class.
- Annotate the method throwTrash() with @AfterClass and JVM will make it run once at the end of the class.
- Annotate the method startMachine() with @Before and JVM will run it before each testcase runs.
- Annotate the method throwCup() with @After and JVM will run it after the execution of each test case.
- Annotate each of these methods HotWater(), Capuccino() and ExpressoCoffee() with @Test and JVM knows that these are the core test cases for the JUnit class to be executed.
Let us quickly have a look at the JUnit lifecycle call back annotations for JUnit 4 vs JUnit 5
JUNIT 4 ANNOTATION | JUNIT 5 ANNOTATION |
---|---|
@Before | @BeforeEach |
@After | @AfterEach |
@BeforeClass | @BeforeAll |
@AfterClass | @AfterAll |
@Test | @Test |
Sequential Workflow Of The Lifecycle Annotations
Given below is the sequential workflow of the lifecycle annotations for JUnit 4:
- The method annotated with @BeforeClass is executed once at the start of the class.
- The method annotated with @Before executes before Testcase 1 begins.
- The method Testcase1 annotated with @Test is the testcase in the class.
- The method annotated with @After runs after Testcase 1 completes execution.
- The method annotated with @Before executes before Testcase 2 begins.
- The method Testcase2 annotated with @Test is the testcase in the class.
- The method annotated with @After runs after Testcase 2 completes execution.
- The method annotated with @AfterClass is executed once at the end of the class after both testcase 1 and 2 are executed.
The sequential workflow of the lifecycle annotations for JUnit 5 is as follows:
- The method annotated with @BeforeAll is executed once at the start of the class.
- The method annotated with @BeforeEach executes before Testcase 1 begins.
- The method Testcase1 annotated with @Test is the testcase in the class.
- The method annotated with @AfterEach runs after Testcase 1 completes execution.
- The method annotated with @BeforeEach executes before Testcase 2 begins.
- The method Testcase2 annotated with @Test is the testcase in the class.
- The method annotated with @AfterEach runs after Testcase 2 completes execution.
- The method annotated with @AfterAll is executed once at the end of the class after both testcase 1 and 2 are executed.
Elaboration On Each Annotation
In this section, let’s deep dive and have a detailed understanding of what each of the lifecycles call back annotation does:
@Before (JUnit 4) /@BeforeEach (JUnit 5):
- The annotated method executes before the execution of each test method in the test class.
- This annotation can be used when you wish to have the resources or test data set up just before the initiation of each test.
- For example, if there are 5 Testcases in a JUnit test class then the method annotated with @Before/@BeforeEach executes 5 times prior to each of the test case’s execution.
@After (JUnit 4) /@AfterEach (JUnit 5):
- The annotated method executes after each test method in the test class executes.
- This annotation can be used when you wish to have to release used resources or test data after each test case runs.
- For example, if there are 5 Testcases in a JUnit test class then the method annotated with @After/@AfterEach executes 5 times after the test cases’ execution.
@BeforeClass (JUnit 4) /@BeforeAll (JUnit 5):
- The annotated method executes before all the test methods in a test class is executed.
- This annotation can be used when you wish to set up resources or test data at the class level.
- As this method is annotated with @BeforeClass/@BeforeAll is executed only once for a test class and the copy of the method gets shared across the class, and the method must be stated static.
- For example, if there are 5 Testcases in a JUnit test class then the method annotated with @BeforeClass/@BeforeAll executes once per test class before any testcase initiates.
@AfterClass (JUnit 4) /@AfterAll (JUnit 5):
- The annotated method executes after all the test methods in a test class executes.
- This annotation can be used when you wish to release the used resources or test data at the class level.
- As this method is annotated with @AfterClass/@AfterAll is executed only once for a test class and the copy of the method gets shared across the class, the method must be stated static.
- For example, if there are 5 Testcases in a JUnit test class then the method annotated with @AfterClass/@AfterAll executes once per test class after all the test cases complete execution.
@Test (JUnit 4 & JUnit 5):
- The @Test annotation is common for JUnit 4 as well as JUnit 5. The annotated methods represent the test cases in the class.
- There could be multiple methods each annotated with @Test in a JUnit class. This implies that a class may have multiple test cases.
- There are different attributes or parameters to Test which one could be passed. You could add a forced time out for a test case or add an exception. This will be covered in detail in a separate tutorial.
- The annotated method cannot be private or static and cannot return any value.
- The @Test method has to be declared as public in JUnit 4 while Junit 5 allows a testcase defined without the access modifier ‘public’ as it considers ‘no access modifier’ as ‘public’ by default.
Basic JUNIT Test Example
A basic JUNIT 4 example for annotations @BeforeClass, @Before, @Test, @After, and @AfterClass was shown through the code with its explanation in our earlier tutorial on ‘Test Fixtures’.
Let’s look at the basic JUnit 5 Program to demonstrate the working of the Lifecycle call-back annotations @BeforeAll, @BeforeEach, @Test, @AfterEach, and @AfterAll.
Code for JUnit5Program.java:
public class JUnit5Program { @BeforeAll public static void preClass() { System.out.println("@BeforeAll – the annotated method runs once before all other methods execute"); } @BeforeEach public void setUp() { System.out.println("_______________________________________________________\n"); System.out.println("@BeforeEach – the annotated method executes before each test "); } @Test public void test_JUnit1() { System.out.println("@Test – this is test case 1"); } @Test public void test_JUnit2() { System.out.println("@Test – this is test case 2"); } @Test public void test_JUnit3() { System.out.println("@Test – this is test case 3"); } @AfterEach public void tearDown() { System.out.println("@AfterEach – the annotated method executes after each test executes"); System.out.println("_______________________________________________________\n"); } @AfterAll public static void postClass() { System.out.println("@AfterAll – the annotated method runs once after all other methods execute"); } }
On execution of the class file, the below result shows up on the Console window.
Additional Annotations – JUnit 4 vs JUnit 5
There are many additional annotations that are used for specific purposes. We will see the list of annotations for JUnit 4 vs JUnit 5 and the purpose it serves in brief.
There will be a detailed tutorial on each of these annotations in our upcoming tutorials.
JUNIT 4 ANNOTATION | JUNIT 5 ANNOTATION | Description in brief |
---|---|---|
@FixMethodOrder | @TestMethodOrder & @Order | 1. These annotations allow the user to choose the order of execution of the methods within a test class |
@Rule & @ClassRule | @ExtendWith | 1. @Rule – The annotation is extended from the class TestRule that helps apply certain rules on the test cases. 2. For Example: creating a temporary folder prior to test case execution and deleting the folder post-execution can be set through a Rule. 3. @Rule is available only in JUnit 4 which can be used in JUnit 5 Vintage, however, @ExtendWith provides a closer feature for JUnit 5 4. Similarly, a global timeout can be set using @Rule. |
NA | @TestFactory | 1. This annotation supported by JUnit 5 only and helps creation of dynamic or runtime tests. 2. It returns a stream of data as collection and cannot use lifecycle callback annotations |
NA | @Nested | 1.This annotation is supported by JUnit Jupiter only 2.It helps us to create nested test cases. 3.For instance, Class 1 with testcase 1 might have a @Nested Class 2 with testcase 2. This makes testcase 2 a nested testcase to testcase 1. Hence, testcase 1 executes, then testcase 2 executes. 4.If the @Nested annotation is not used the nested class will not execute. |
@Category | @Tag | 1.This annotation helps for tagging and filtering the tests 2.You may include tests for execution or exclude them by filtering based on the categories they fall in. |
@RunWith(Parameterized.class) @Parameterized.Parameters | @ParameterizedTest and @ValueSource | 1. This annotation is used to run a method with test data variations multiple times. 2.JUnit 4 supports @RunWith and @Parameters while JUnit 5 Jupiter supports @ParameterizedTest with @ValueSource |
@RepeatedTest | 1.JUnit 5 supports repeated execution of the test method for a certain number of times using @RepeatedTest annotation | |
@DisplayName | 1. A user-defined name can be given to a test method or class for display purposes. | |
@TestInstance (LifeCycle.PER_CLASS) and @TestInstance (LifeCycle.PER_METHOD) | 1. JUnit 5 supports the configuration of the lifecycle of tests. 2. Both JUnit 4 and 5 follow the default per method lifecycle call-back while per-class configuration can also be done. |
References => JUnit 4, JUnit 5
Conclusion
- We learned about the lifecycle call-back annotations and the sequential workflow in which the test methods execute based on their annotations.
- We learned the annotations used for JUnit 4 and the annotations for JUnit 5.
- We also learned about additional annotations that JUnit 4 supports and those that support JUnit 5 only.
=> Watch Out The Simple JUnit Training Series Here.