Testing in Java
JUnit 5 Fundamentals
Master the structure, lifecycle, and assertions of the modern Java testing framework.
Interview: Essential for standard unit testing questions. Expect discussions on test lifecycles (PER_METHOD vs PER_CLASS), nested and parameterized tests, and exception assertion syntax.
JUnit 5 is the industry-standard framework for writing developer-led unit tests in Java. It is composed of three sub-modules: JUnit Platform (launcher engine), JUnit Jupiter (new programming and extension model), and JUnit Vintage (backward compatibility engine).
Core Idea
JUnit 5 separates the test engine from the launcher platform, allowing modern modular design and custom test executions.
Why It Matters
Structured unit tests provide safety nets during refactoring, serve as executable documentation, and enforce clean design.
Interview Lens
Tricky questions often focus on the class-level lifecycle annotation behaviors and the execution order of lifecycle hooks.
Test Lifecycles
By default, JUnit creates a new instance of the test class for every single test method execution (Lifecycle.PER_METHOD). This isolates tests and prevents mutable instance state from bleeding between test executions. You can override this behavior using @TestInstance(Lifecycle.PER_CLASS), which executes all tests in the same instance, allowing @BeforeAll and @AfterAll hooks to be non-static.
Code Walkthrough
This class demonstrates assertions, parameter inputs, and exception expectations using modern JUnit Jupiter features.
import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*;@DisplayName("Standard Calculator Suite") class CalculatorTest {
private Calculator calculator;
@BeforeEach void init() { calculator = new Calculator(); }
@Test @DisplayName("Basic Addition Test") void testAddition() { assertAll("Grouped Assertions", () -> assertEquals(5, calculator.add(2, 3)), () -> assertEquals(0, calculator.add(-1, 1)) ); }
@Test @DisplayName("Exception Propagation Test") void testDivisionByZero() { ArithmeticException exception = assertThrows( ArithmeticException.class, () -> calculator.divide(10, 0), "Should throw division by zero exception" ); assertEquals("/ by zero", exception.getMessage()); }
@ParameterizedTest @ValueSource(ints = {2, 4, 6, 8, 10}) @DisplayName("Verification of Even Numbers") void testIsEven(int number) { assertTrue(calculator.isEven(number)); } }
Interview-Relevant Information
Q: What is the difference between JUnit 4 and JUnit 5 annotations?
Answer: JUnit 5 changes several major annotation packages. For example, @Test is now in org.junit.jupiter.api instead of org.junit. Also, @Before and @After are replaced by @BeforeEach and @AfterEach, while @BeforeClass and @AfterClass are replaced by @BeforeAll and @AfterAll.
Q: How do you perform grouped assertions in JUnit 5?
Answer: Grouped assertions are made using assertAll(String heading, Executable... executables). Unlike standard inline assertions where a failure aborts the test immediately, assertAll ensures all executables are evaluated, reporting all failures collectively in a single detailed error log.
Quick Checklist
Do you understand when to use PER_METHOD vs PER_CLASS? Can you write an assertThrows block to verify an expected exception? If yes, you are prepared to build sound unit testing foundations.
Use Cases
Asserting expected mathematical outputs for internal calculation modules.
Validating parameterized edge inputs (null, empty strings, maximum limits) against business rules.
Common Mistakes
Mixing JUnit 4 imports (like org.junit.Test) with JUnit 5 annotations, resulting in test runners ignoring target execution blocks.
Relying on external state or database records, making unit tests non-deterministic and flaky.