Professional Java ToolkitPro· 40 min read

Unit Testing with JUnit

Unit tests are small programs that automatically check your code is correct, so a future change cannot silently break it.

What you will learn

  • Explain what a unit test is and why it matters
  • Write a JUnit test with assertions
  • Read a passing and a failing test result

What is a unit test?

A unit test is a small piece of code whose only job is to check that another piece of code does what it should. You give your method some input, then assert (state confidently) what the answer must be. A testing tool runs all your assertions automatically and tells you which passed and which failed.

Why bother? Because programs change. When you fix one thing, you can easily break another without noticing. Tests are a safety net: run them after any change and they instantly catch anything you broke. The standard testing tool for Java is JUnit (we use JUnit 5, the current version).

The code we want to test

Say we have a small calculator class. It is ordinary Java — nothing test-specific about it.

The class under test
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public boolean isEven(int n) {
        return n % 2 == 0;
    }
}

Note: Output: (No output — this is just the class we want to check. Next we write a separate test class that calls these methods and verifies their answers.)

Writing the test

A test is a method marked with @Test (an annotation — a label starting with @ that tells JUnit "this method is a test"). Inside, we call the real method and use an assertion to state the expected result. assertEquals(expected, actual) checks two values match; assertTrue(...) checks a condition is true.

Two JUnit tests with assertions
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class CalculatorTest {

    @Test
    void addingTwoNumbers() {
        Calculator calc = new Calculator();
        int result = calc.add(2, 3);
        assertEquals(5, result);     // expected 5, got result
    }

    @Test
    void fourIsEven() {
        Calculator calc = new Calculator();
        assertTrue(calc.isEven(4));  // must be true
    }
}

Note: Output (from the test runner): CalculatorTest > addingTwoNumbers() PASSED CalculatorTest > fourIsEven() PASSED Tests run: 2, Passed: 2, Failed: 0 JUnit found both @Test methods and ran them. assertEquals(5, result) passed because add(2, 3) really is 5, and assertTrue(calc.isEven(4)) passed because 4 is even. Two green ticks mean the calculator behaves as promised.

What a failing test looks like

The value of a test shows when something is wrong. Suppose someone accidentally changes add to return a - b;. The test that expects 5 now fails, and JUnit tells you exactly how:

The same test, now run against broken code
@Test
void addingTwoNumbers() {
    Calculator calc = new Calculator();
    assertEquals(5, calc.add(2, 3));   // add is now broken
}

Note: Output: CalculatorTest > addingTwoNumbers() FAILED expected: <5> but was: <-1> Tests run: 1, Passed: 0, Failed: 1 The assertion expected 5 but the broken add returned -1, so JUnit reports a clear FAILED with both numbers. You learn instantly that add is wrong — and which test caught it — instead of finding out later when a user hits the bug.

A good test, step by step

Most tests follow a simple three-part shape, often called Arrange, Act, Assert:

  1. Arrange: set up what you need — create the object and any input values.
  2. Act: call the method you are testing and capture its result.
  3. Assert: state what the result must be with an assertion (assertEquals, assertTrue, assertFalse).
  4. Run them all: your build tool runs every @Test automatically — mvn test with Maven, gradle test with Gradle.

Notice how this ties the toolkit together: JUnit is added as a dependency (last lesson), and the build tool runs your tests as part of mvn package. Testing is not a separate world — it is built into the normal Java workflow.

Tip: Aim to test the tricky and important parts: calculations, validation, edge cases (zero, negative, empty). A handful of focused tests on the logic that matters is far more useful than trying to test every trivial line.

Watch out: A test that never fails is not proof your code is perfect — it only checks the cases you wrote. Tests catch the mistakes you thought to check for, so include the awkward inputs (empty input, the boundary value, the negative number) where bugs love to hide.

Q. What does assertEquals(5, calc.add(2, 3)) do in a JUnit test?

Answer: assertEquals compares an expected value with the actual result. If they match the test passes; if they differ JUnit reports a failure showing both values, so you immediately know the method is wrong.

✍️ Practice

  1. Write a JUnit test for a multiply(int a, int b) method that asserts multiply(4, 5) equals 20.
  2. Write a test using assertFalse that checks isEven(7) returns false.

🏠 Homework

  1. Pick a method you have written earlier in this course (for example isEven or a price calculator) and write two or three JUnit tests for it: one normal case and at least one edge case. Note what each test is checking and why.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →