Table of contents
1.
Introduction
2.
Test Lifecycle
3.
The @TestInstance Annotation
3.1.
Test Lifecycle Per Method
3.1.1.
Program:
3.1.2.
Output:
3.2.
Test Lifecycle Per Class
3.2.1.
Program:
3.2.2.
Output:
4.
@BeforeAll and @AfterAll Annotations
4.1.
Program:
4.2.
Output:
5.
FAQs
6.
Key Takeaways
Last Updated: Mar 27, 2024

JUnit Test Instance

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Test classes can often contain member variables that refer to the system under test, mocks, or data resources used in the test. So, JUnit 4 and JUnit 5 create a new test class instance before running each test method by default. That provides a clean separation of state between multiple tests. 

In this article, we will learn about the @TestInstance annotation, default lifecycles for a test, and how we can modify the lifecycle of the test class using the @TestInstance annotation in JUnit.

Test Lifecycle

JUnit manages the execution of multiple test methods that a test class usually contains in the form of a lifecycle. 

We observe the complete lifecycle of a test case in three phases with the help of annotations.

  • The Setup phase puts the test infrastructure in place. JUnit provides class-level setup and method-level setup, @BeforeAll and @BeforEach, respectively. Heavy objects like database connections generally create in class level setup, while lightweight objects like test objects reset in the method level setup.
  • In the Test Execution phase, the test execution and assertion happen. The execution result signifies success or failure.
  • The Cleanup phase cleans up the test infrastructure setup in the first phase. Like setup, the teardown also happens at class level and method level with @AfterAll and @AfterEach, respectively.

The @TestInstance Annotation

The @TestInstance annotation allows you to configure the lifecycle of JUnit tests.

The @TestInstance has two modes. One is LifeCycle.PER_METHOD is the default, and the other is LifeCycle.PER_CLASS allows asking JUnit to create only one instance of the test class and reuse it between tests.

Test Lifecycle Per Method

The default lifecycle for all the tests in JUnit is the Lifecycle Per-Method mode. JUnit creates a new test instance for each test method, test factory method, or test template method when using this mode. 

In the following example, we will test the java.lang.Math.class using the @TestInstance(Lifecycle.PER_METHOD)

Program:

package testinstance;


import static org.junit.jupiter.api.Assertions.*;


import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;


@TestInstance(TestInstance.Lifecycle.PER_METHOD)
public class testinstance {
  
  private int result = 5;
  
  @Test
  void test_Add() {
    result = Math.addExact(result, 5);
    System.out.println("test_Add(5,5) => "+result);
    assertEquals(10, result);
  }
  
  @Test
  void test_Multiply() {
    result = Math.multiplyExact(result, 5);
    System.out.println("test_Multiply(5,5) => "+result);
    assertEquals(25, result);
  }
 
}
You can also try this code with Online Java Compiler
Run Code

Output:

These tests in this example pass because a new instance of testinstance creates before calling each test method. That means that the value of the variable result is always set to 5 before the execution of each test.

Test Lifecycle Per Class

A new test instance is created once per test class when using the Lifecycle Per Method mode. This mode shares the test instance state between test methods in a given test class and between non-static @BeforeAll and @AfterAll. 

When instantiating a class before every test is quite expensive, this annotation is functional. A similar example for testing java.lang.Math.class will give different results here.

Program:

package testinstance;


import static org.junit.jupiter.api.Assertions.*;


import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;


@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class testinstance {
  
  private int result = 5;
  
  @Test
  void test_Add() {
    result = Math.addExact(result, 5);
    System.out.println("test_Add(5,5) => "+result);
    assertEquals(10, result);
  }
  
  @Test
  void test_Multiply() {
    result = Math.multiplyExact(result, 5);
    System.out.println("test_Multiply(5,5) => "+result);
    assertEquals(50, result);
  }
 
}
You can also try this code with Online Java Compiler
Run Code

Output:

The output on running the test in the Per Class mode demonstrates the concept of this lifecycle mode.

@BeforeAll and @AfterAll Annotations

You can use the @BeforeAll annotations  ​​when you need an object to exist across multiple tests while using JUnit 5.

In the Default PER_METHOD mode of test instance lifecycle, @BeforeAll and @AfterAll callback methods must be static; otherwise, you will get a compile-time error as Mocha currently does not support these methods to be non-static in PER-METHOD. if you specify Lifecycle.PER_CLASS as your lifecycle mode, you can make @BeforeAll and @AfterAll methods non-static.

JUnit 4 addresses this with the @BeforeClass annotation.

In the following example, we can see the non-static implementation of the @BefoeAll and @AfterAll annotations.

Program:

package testinstance;


import static org.junit.jupiter.api.Assertions.*;


import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;


@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class testinstance {
  
 @BeforeAll
  void beforeAll() {
    // here common setup for all tests
    System.out.println("--- Before starting tests --- ");
  }
  
  @BeforeEach
  void beforeEach(TestInfo testInfo) {
    // here initialize setup for each test
    System.out.println("Before Strat test >>>> "+testInfo.getTestMethod().get().getName());
  }
  @Test
  void test_Add() {
    System.out.println("MathUtil.add(5, 5) => "+Math.addExact(5, 5));
    assertEquals(10, Math.addExact(5, 5));
  }
  
  @Test
  void test_Multiply() {
    System.out.println("MathUtil.multiple(5, 5) => "+Math.multiplyExact(5, 5));
    assertEquals(25, Math.multiplyExact(5, 5));
  }
  
  @AfterEach
  void afetrEach(TestInfo testInfo) {
    // here cleanup setup after each test completes
    System.out.println("After complete test >>>> "+testInfo.getTestMethod().get().getName());
  }
  
  @AfterAll
  void afetrAll() {
    // here cleanup setup after all tests completes
    System.out.println("--- After tests completed ---");
  }  
}
You can also try this code with Online Java Compiler
Run Code

Output:

Check out JUnit Interview Questions here.

FAQs

  1. How do you test instance methods?
    To test an instance method, you must create an instance to call the method. Further, you may need to make more instances of the class. 
     
  2. Why does JUnit creates a new instance for each test?
    Creating a new instance of the test class before invoking each @Test method helps provide independence between test methods and avoids unintentional side effects in the test code in JUnit. We can't reuse instance variable values across test methods because each test method runs on a new test class instance.
     
  3. Do JUnit test methods must compulsorily return a value?
    If a JUnit test method is declared to return anything, the file will compile successfully. But the execution will fail because JUnit requires you to declare all test methods to return void.
     
  4. How do you assert Exceptions in JUnit?
    The JUnit 5 Jupiter assertions API includes the assertThrows method for asserting exceptions. That takes the expected exception type and an Executable functional interface where you can pass the code under test through a lambda expression. If the test throws the expected exception, assertThrows returns the exception, enabling you to assert the message.
    In JUnit 4, you can use the expected attribute of the @Test annotation to declare that you expect the test to throw an exception anywhere in the annotated test method.
     
  5. What is the difference between Lifecycle.PER_CLASS and Lifecycle.PER_METHOD ?
    Refer to the table below:

 

Key Takeaways

Through this article, we have learned about the Test Lifecycle and how we can use the @TestInstance Annotations to configure the lifecycle of JUnit 5 tests. We also introduced the @BeforeAll and @BeforeClass implementations to configure the lifecycle of tests.

A good understanding of the concept of Test Lifecycle, its configuration, and implementation is critical while using JUnit 4 and JUnit 5 to perform Unit Tests. You can also learn about JUnit Vintage Engine compatibility between JUnit 4 and JUnit 5.

Hence the learning never stops, so be sure to check the Modules article on Coding Ninjas and head over to our practice platform Coding Ninjas Studio to practice top problems, attempt mock tests, read interview experiences, and much more. Till then, Happy Learning!

Live masterclass