← Back to Advanced Java Course | Chapter 8: Database & JPA | Lesson 4 of 4

Java Unit Testing (JUnit)

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

java
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");
	}
}

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

java
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();
	}
}

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

java
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");
	}
}

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

java
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
	}
}

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

java
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");
	}
}
🔒

Chapter Quiz — Complete all 4 topics to unlock

0/4 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.