Java Unit Testing (JUnit)
In this page:
Assertions
Assertions are how a test actually verifies that code produced the expected result -- methods like assertEquals or assertTrue compare an actual value against an expected one. If the comparison fails, the assertion throws, which JUnit catches and reports as a failed test rather than letting the exception crash the whole run.
Example: Assertions
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class Main {
@Test
void testAddition() {
int result = 2 + 3;
assertEquals(5, result); // fails (and is reported) if this doesn't hold
}
public static void main(String[] args) {
new Main().testAddition();
System.out.println("Assertion passed");
}
}
Login to try C/C++/Java/PHP code in the editor
The Test Lifecycle
Because unit tests should run independently of each other and of execution order, JUnit provides lifecycle annotations like @BeforeEach and @AfterEach to prepare a test's dependencies fresh before it runs and clean them up afterward, preventing state from one test from leaking into the next.
Example: The Test Lifecycle
import org.junit.jupiter.api.*;
public class Main {
@BeforeEach
void setup() { System.out.println("Fresh setup before each test"); }
@AfterEach
void cleanup() { System.out.println("Cleanup after each test"); }
@Test
void sampleTest() { System.out.println("Running test"); }
public static void main(String[] args) {
Main m = new Main();
m.setup(); m.sampleTest(); m.cleanup();
}
}
Login to try C/C++/Java/PHP code in the editor
Testing Exceptions
You should explicitly test that your methods throw the right exceptions when given invalid input, using assertThrows() to assert both that an exception is thrown and that it's the correct type, rather than only testing the happy path and hoping bad input fails loudly elsewhere.
Example: Testing Exceptions
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class Main {
static void divide(int a, int b) {
if (b == 0) throw new ArithmeticException("divide by zero");
}
@Test
void testDivideByZero() {
assertThrows(ArithmeticException.class, () -> divide(10, 0));
}
public static void main(String[] args) {
new Main().testDivideByZero();
System.out.println("Exception correctly thrown and verified");
}
}
Login to try C/C++/Java/PHP code in the editor
Dynamic Parameterized Tests
Parameterized tests (via @ParameterizedTest) let you run the same test logic repeatedly against a table of different inputs and expected outputs. This is far less repetitive than writing a near-identical test method for every edge case you want to check.
Example: Dynamic Parameterized Tests
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class Main {
@ParameterizedTest
@ValueSource(ints = {2, 4, 6})
void testEven(int number) {
System.out.println(number + " is even: " + (number % 2 == 0));
}
public static void main(String[] args) {
Main m = new Main();
for (int n : new int[]{2, 4, 6}) m.testEven(n); // one method, many inputs
}
}
Login to try C/C++/Java/PHP code in the editor
Suite Runners
To run your tests across an entire application or module in one pass, you aggregate individual test classes inside a test suite configuration, which JUnit or your build tool can then execute as a single unit -- useful for CI pipelines that need one pass/fail signal for the whole codebase.
Example: Suite Runners
import org.junit.platform.suite.api.*;
@Suite
@SelectClasses({}) // aggregates individual test classes for one CI pass/fail signal
public class Main {
public static void main(String[] args) {
System.out.println("Suite runs every selected test class as a single unit");
}
}
Login to try C/C++/Java/PHP code in the editor
Chapter Quiz — Complete all 4 topics to unlock
0/4 topics done
Complete these topics first: