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

Java Unit Testing (JUnit)

JUnit lets you write small tests that check your code gives the right answers, like a teacher automatically marking homework. You can run them anytime to catch new mistakes.
Syntax
java
@Test
void testName() {
    // arrange
    // act
    assertEquals(expected, actual);
}

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.* so it can be used by its short name
import org.junit.jupiter.api.*;
// Define the class Main
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"); }
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new Main object and store it in m
		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 org.junit.jupiter.api.Assertions.* so it can be used by its short name
import static org.junit.jupiter.api.Assertions.*;
// Import org.junit.jupiter.api.Test so it can be used by its short name
import org.junit.jupiter.api.Test;
// Define the class Main
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));
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		new Main().testDivideByZero();
		// Print a line to the console
		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");
	}
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Writing assertEquals(result, 5) in the wrong order, when it is assertEquals(expected, actual), which makes failure messages misleading.
  2. Testing exceptions with a try/catch instead of assertThrows, so the test passes even when no exception is thrown.
  3. Making tests depend on each other or on execution order through shared state, when each test should run on its own.
Chapter Summary
  • JDBC connects Java programs to databases and performs create, read, update, and delete operations.
  • ORM with Hibernate maps Java objects to database tables.
  • JUnit provides a way to write unit tests for your code.
🔒

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.