Skip to content

Expert · JDBC, Maven, Gradle and JUnit · topic 50 of 60 · 30 min

Unit testing with JUnit

Explanation

JUnit is the standard Java testing framework. A test is a method annotated @Test that arranges data, acts by calling your code, and asserts the result with assertEquals, assertTrue or assertThrows. @BeforeEach sets up fresh objects, @ParameterizedTest runs one test with many inputs, and Mockito replaces slow or external dependencies with mocks. Tests run with mvn test or ./gradlew test.

Why unit tests?

A unit test checks one small piece of code (a method or class) automatically. A good suite of hundreds of tests runs in seconds and tells you immediately when a change breaks something, so you can refactor and add features without fear. Most Java teams require tests for every change, and interviewers often ask you to write one.

java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class GstCalculatorTest {

    private GstCalculator calc;

    @BeforeEach
    void setUp() {
        calc = new GstCalculator(18);       // a fresh object for every test
    }

    @Test
    void addsEighteenPercent() {
        assertEquals(1180.0, calc.withGst(1000), 0.001);   // expected, actual, tolerance
    }

    @Test
    @DisplayName("negative amounts are rejected")
    void rejectsNegative() {
        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> calc.withGst(-5));
        assertTrue(e.getMessage().contains("negative"));
    }
}

JUnit 5 (the "Jupiter" API in org.junit.jupiter.api) is the common version today; JUnit 6 continues the same Jupiter API. Test classes and methods do not need to be public. Each test method runs on a NEW instance of the test class.

Arrange, act, assert

Structure each test in three parts: arrange (set up the inputs), act (call the method), assert (check the result). One behaviour per test, with a name that reads like a sentence: rejectsNegative, appliesFreeDeliveryAt499. When it fails, the name says what broke.

Parameterized tests

java
@ParameterizedTest
@CsvSource({
    "1000, 1180.0",
    "0, 0.0",
    "99.5, 117.41"
})
void gstTable(double amount, double expected) {
    assertEquals(expected, calc.withGst(amount), 0.01);
}

@ParameterizedTest
@ValueSource(strings = {"", " ", "12345", "ABCDE12345"})
void invalidPans(String pan) {
    assertFalse(PanValidator.isValid(pan));
}
Needs the junit-jupiter-params artifact (included in the junit-jupiter aggregate).

Mocking with Mockito

A class that sends SMS, calls a payment gateway or reads a database is hard to unit test: slow, costly, unpredictable. Mockito creates mock objects that stand in for those dependencies, return answers you choose, and record how they were called.

java
import static org.mockito.Mockito.*;

@Test
void sendsOtpOnce() {
    SmsSender sms = mock(SmsSender.class);                 // a fake SmsSender
    OtpService service = new OtpService(sms);              // inject it
    service.sendOtp("9876543210", "482913");
    verify(sms, times(1)).send("9876543210", "Your OTP is 482913");
}

@Test
void declinedPaymentDoesNotEmail() {
    PaymentGateway gateway = mock(PaymentGateway.class);
    Mailer mailer = mock(Mailer.class);
    when(gateway.charge(anyLong())).thenReturn(false);      // stub the answer
    new Checkout(gateway, mailer).pay(49_900);
    verifyNoInteractions(mailer);
}
Careful: Tests that depend on the current time, random numbers, real networks, or on running in a particular order are flaky: they pass sometimes and fail sometimes. Inject a Clock, fix random seeds, mock external services, and give each test its own fresh data. Also, a test that never fails is useless: break the code once and watch the test go red.
Think of it like this: Unit tests are like the quality-check counter in a garment factory: every shirt is checked for buttons, stitching and size automatically before it ships. Mocks are like the dummy mannequin used for fitting, so you do not need a real customer standing there all day.
Tip: When you fix a bug, first write a test that reproduces it (it fails), then fix the code (it passes). That bug can never come back silently.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Unit testing with JUnit · 1/6

JUnit

In this lesson you will write automated tests with JUnit.

Prefer a video by a teacher? Find free videos on “junit 5 tutorial assertThrows parameterized test mockito” on YouTube ↗

In real life: Testing EMI calculations before a rate change

A lending company's EMI calculator has 60 parameterised test cases covering different principals, rates, tenures and the zero-rate edge case. When the team refactored the formula for performance, the suite caught a rounding change in two cases before it reached customers.

java
@ParameterizedTest
@CsvSource({ "500000, 9, 60, 10379.18", "100000, 0, 10, 10000.00" })
void emiTable(double principal, double rate, int months, double expected) {
    assertEquals(expected, Emi.emi(principal, rate, months), 0.01);
}

Practice (0/3 done)

Task 1 · Question

Which assertion checks that a method throws IllegalArgumentException?

Task 2 · Question

Why use @BeforeEach to create the object under test?

Task 3 · Practice

For a class BankAccount (deposit, withdraw that throws IllegalStateException on insufficient balance), write a JUnit 5 test class with at least 5 tests: a normal deposit, a normal withdrawal, withdrawing more than the balance (assertThrows), a parameterised test of several deposits, and a test that the balance is unchanged after a failed withdrawal. Run them with mvn test.

Quick quiz

Question 1

1.Which package holds the JUnit 5 (Jupiter) annotations?

Question 2

2.What does Mockito's when(x.call()).thenReturn(5) do?

Question 3

3.Which command runs the tests in a Maven project?

Question 4

4.What makes a test "flaky"?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which package holds the JUnit 5 (Jupiter) annotations?

Answer: org.junit.jupiter.api

org.junit was JUnit 4; junit.framework was JUnit 3.

What does Mockito's when(x.call()).thenReturn(5) do?

Answer: Makes the mock return 5 for that call

That is stubbing.

Which command runs the tests in a Maven project?

Answer: mvn test

The test phase runs the Surefire plugin.

What makes a test "flaky"?

Answer: It sometimes fails without any code change

Usually time, randomness, network or shared state.

In Arrange, Act, Assert, what happens in the Act step?Related

Answer: You call the code being tested

Arrange prepares, Act calls the function once, Assert checks the result.

From Unit testing: checking one piece at a time

Which annotation marks a JUnit test function?Related

Answer: @Test

JUnit runs every @Test function.

From Unit tests with JUnit and coroutines

What does Arrange-Act-Assert mean?Related

Answer: Set up the data, call the code, check the result

It is the simple shape of almost every test.

From Testing basics: unit, integration and end-to-end

What does Arrange-Act-Assert describe?Related

Answer: The structure of a test: set up, call, check

It keeps tests readable.

From Unit testing with xUnit

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “JDBC, Maven, Gradle and JUnit”. The module exam comes next.
  2. Module 10 exam: JDBC, Maven, Gradle and JUnit · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Spring Boot basics. 60 topics left in the course before the final exam.

← Previous: Gradle in brief