Java JUnit Introduction
What JUnit is, how to add it to a Java project, and writing your first JUnit test.
Java JUnit Introduction
JUnit is the de facto standard framework for writing automated tests in Java. A test is just a small method that runs a piece of your code and asserts that it behaved as expected; JUnit's job is to discover those methods, run each one in isolation, check the assertions, and report which passed and which failed. The current generation, JUnit 5 (also called JUnit Jupiter), ships as a set of small libraries you add to your build — it is not part of the JDK — and it powers the mvn test / gradle test step that gates almost every Java project's CI.
Why a testing framework at all
You could verify code by hand with main methods and println — but that scales badly. A framework gives you four things you would otherwise rebuild yourself:
- Discovery — it finds every
@Testmethod automatically; you never maintain a list. - Isolation — each test gets a fresh fixture, so one test cannot corrupt another.
- Assertions — a rich vocabulary (
assertEquals,assertThrows, …) that produces precise failure messages. - Reporting — a uniform pass/fail summary the build tool and IDE understand.
Get these right once and tests become cheap to write, which is the whole point: cheap tests get written, and code that is tested can be changed without fear.
Adding JUnit to a project
JUnit 5 is a dependency, declared in your build file. With Maven, the junit-jupiter aggregator pulls in the API (to compile against) and the engine (to run with):
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.3</version>
<scope>test</scope>
</dependency>With Gradle it is two lines plus the useJUnitPlatform() switch:
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
}
test {
useJUnitPlatform()
}Test sources live under src/test/java, mirroring the package of the class they exercise. The test scope keeps JUnit out of your production artifact.
Your first test
A JUnit test is an ordinary method annotated with @Test. Inside it you call the code under test and assert on the result. Here is a Calculator and a test class for it:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class CalculatorTest {
private final Calculator calc = new Calculator();
@Test
void addReturnsSum() {
assertEquals(5, calc.add(2, 3));
}
@Test
void divideByZeroThrows() {
assertThrows(ArithmeticException.class, () -> calc.divide(1, 0));
}
}Notice the pattern every test follows — Arrange a fixture, Act by calling the method, Assert the outcome. Test methods are package-private (no public needed in JUnit 5) and return void. Run mvn test and the build goes green only if every assertion holds.
The annotations and assertions you will use most
JUnit's surface area is small. These few members cover the vast majority of real tests:
| Member | Package / class | Purpose |
|---|---|---|
@Test | org.junit.jupiter.api | Marks a method as a test |
@BeforeEach / @AfterEach | org.junit.jupiter.api | Run before/after every test (set up / tear down fixtures) |
@BeforeAll / @AfterAll | org.junit.jupiter.api | Run once before/after all tests in the class |
@DisplayName | org.junit.jupiter.api | A human-readable name for reports |
@Disabled | org.junit.jupiter.api | Temporarily skip a test |
assertEquals(exp, act) | Assertions | Fail unless the two are equal |
assertTrue / assertFalse | Assertions | Fail unless a boolean holds |
assertThrows(type, exec) | Assertions | Fail unless the lambda throws that exception |
assertNull / assertNotNull | Assertions | Fail on the wrong nullness |
@BeforeEach is what gives every test a clean slate — JUnit constructs a fresh instance of the test class for each @Test, then runs the setup, so state never leaks between tests.
What JUnit does for you, in one runnable file
The code runner here has no JUnit on its classpath (it is an external library, not part of the JDK), so the example below re-implements JUnit's core loop in plain Java: a fixture re-created before each test, a small set of assertXxx helpers, a list of test methods run independently, and a pass/fail tally at the end. This is exactly the machinery JUnit automates — seeing it bare makes the real framework obvious. One test fails on purpose so you can see what red looks like.
What to take from the run:
- The three sound tests print
PASSand the fourth printsFAIL deliberatelyFailing -> expected <10> but was <5>— a precise failure message, not just "a test failed." That diff (expected … but was …) is exactly what JUnit'sassertEqualsgives you, and it is what makes a red test diagnosable at a glance. setUp()runs before every test, socalcis a freshCalculatoreach time. This is the@BeforeEachcontract: tests are isolated, and the order they run in can never matter because none of them shares mutable state.divideThrowsOnZeropasses by asserting that an exception is thrown —assertThrowsmakes "this should fail" a first-class, positive assertion instead of a fragile try/catch. Expected exceptions are behavior worth testing, not errors to swallow.- The final tally —
Tests run: 4, Passed: 3, Failed: 1, Assertions: 5— is the report. One failing test out of four still flips the whole build toRED; CI treats any failure as a stop, which is why a green suite is meaningful. - Nothing here imported JUnit, yet the shape is identical: annotated-method discovery, per-test setup, assertions, summary. JUnit's value is that it automates this loop (and adds discovery, parallelism, parameterized tests, and IDE integration) so you write only the test bodies.
What the rest of this part covers
Writing and organizing test classes, the full lifecycle annotations, the complete assertion catalogue, parameterized and repeated tests, assumptions, exception and timeout testing, and mocking collaborators with Mockito. The next chapter starts where every suite begins: defining a test class and running it.
Practice
In JUnit 5, what does annotating a method with @BeforeEach guarantee about the test fixture?