How do I start unit testing in Java with JUnit?

Unit testing in Java means testing small pieces of code — usually one method or one class — in isolation. The most common testing framework for modern Java projects is JUnit 5, also known as JUnit Jupiter.

This guide shows the basic steps to start writing unit tests with JUnit.


1. Add JUnit to Your Project

If you use Maven, add JUnit 5 to your pom.xml:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.4</version>
    <scope>test</scope>
</dependency>

You should also make sure Maven Surefire can run JUnit 5 tests:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.2</version>
        </plugin>
    </plugins>
</build>

If you use Gradle, add:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
}

test {
    useJUnitPlatform()
}

2. Create a Class to Test

Suppose you have a simple calculator class:

package org.kodejava;

public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Divider cannot be zero");
        }
        return a / b;
    }
}

This class has two methods:

  • add() returns the sum of two numbers.
  • divide() divides two numbers and rejects division by zero.

3. Create a Test Class

JUnit test classes are usually placed under:

src/test/java

For the Calculator class, create:

src/test/java/org/kodejava/CalculatorTest.java

Example test class:

package org.kodejava;

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 calculator = new Calculator();

    @Test
    void addReturnsSumOfTwoNumbers() {
        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }

    @Test
    void divideReturnsQuotient() {
        int result = calculator.divide(10, 2);

        assertEquals(5, result);
    }

    @Test
    void divideThrowsExceptionWhenDividerIsZero() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> calculator.divide(10, 0)
        );

        assertEquals("Divider cannot be zero", exception.getMessage());
    }
}

4. Understand the Basic JUnit Annotations

The most important annotation is:

@Test

It marks a method as a test method.

Example:

@Test
void addReturnsCorrectResult() {
    assertEquals(4, 2 + 2);
}

Common JUnit 5 annotations include:

Annotation Purpose
@Test Marks a method as a test
@BeforeEach Runs before each test method
@AfterEach Runs after each test method
@BeforeAll Runs once before all tests
@AfterAll Runs once after all tests
@Disabled Temporarily disables a test

Example using @BeforeEach:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addReturnsSumOfTwoNumbers() {
        assertEquals(5, calculator.add(2, 3));
    }
}

5. Use Assertions

Assertions check whether the result is what you expect.

Common assertions:

assertEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertThrows(ExceptionType.class, executable);

Example:

package org.kodejava;

import org.junit.jupiter.api.Test;

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

class StringTest {

    @Test
    void stringShouldContainText() {
        String message = "Hello JUnit";

        assertNotNull(message);
        assertTrue(message.contains("JUnit"));
        assertEquals(11, message.length());
    }
}

6. Follow the Arrange, Act, Assert Pattern

A common structure for unit tests is:

  1. Arrange — prepare input data and objects.
  2. Act — call the method being tested.
  3. Assert — verify the result.

Example:

@Test
void addReturnsSumOfTwoNumbers() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.add(2, 3);

    // Assert
    assertEquals(5, result);
}

This makes tests easier to read and maintain.


7. Run the Tests

With Maven:

mvn test

With Gradle:

./gradlew test

Most IDEs also let you right-click the test class or test method and choose Run Test.


8. Naming Test Methods

Use descriptive names, so it is clear what behavior is being tested.

Good examples:

void addReturnsSumOfTwoNumbers()
void divideThrowsExceptionWhenDividerIsZero()
void loginFailsWhenPasswordIsInvalid()

Avoid vague names like:

void test1()
void testAdd()
void shouldWork()

9. What Should You Test?

Good candidates for unit tests include:

  • Business rules
  • Calculations
  • Validation logic
  • Conditional logic
  • Exception handling
  • Data transformation methods

For example, test things like:

discount is applied correctly
invalid email is rejected
zero quantity throws an exception
user cannot withdraw more than their balance

You usually do not need to unit test:

  • Simple getters and setters
  • Framework-generated behavior
  • Code with no meaningful logic
  • External services directly

10. Example: Testing a Realistic Service Class

Class to test:

package org.kodejava.order;

public class DiscountService {

    public double applyDiscount(double price, double discountPercent) {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }

        if (discountPercent < 0 || discountPercent > 100) {
            throw new IllegalArgumentException("Discount must be between 0 and 100");
        }

        return price - (price * discountPercent / 100);
    }
}

Test class:

package org.kodejava.order;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class DiscountServiceTest {

    private final DiscountService discountService = new DiscountService();

    @Test
    void applyDiscountReturnsDiscountedPrice() {
        double result = discountService.applyDiscount(100.0, 10.0);

        assertEquals(90.0, result);
    }

    @Test
    void applyDiscountRejectsNegativePrice() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> discountService.applyDiscount(-100.0, 10.0)
        );

        assertEquals("Price cannot be negative", exception.getMessage());
    }

    @Test
    void applyDiscountRejectsInvalidDiscountPercent() {
        assertThrows(
                IllegalArgumentException.class,
                () -> discountService.applyDiscount(100.0, 120.0)
        );
    }
}

For floating-point values, you can also provide a delta:

assertEquals(90.0, result, 0.001);

Summary

To start unit testing in Java with JUnit:

  1. Add JUnit 5 to your project.
  2. Put test classes under src/test/java.
  3. Mark test methods with @Test.
  4. Use assertions such as assertEquals() and assertThrows().
  5. Follow the Arrange, Act, Assert pattern.
  6. Run tests with Maven, Gradle, or your IDE.

A simple JUnit test looks like this:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    void addReturnsSum() {
        Calculator calculator = new Calculator();

        assertEquals(5, calculator.add(2, 3));
    }
}

How do I write unit tests for Spring components?

Writing Unit Tests for Spring Components

For Spring components, you usually want to test business logic without starting the full Spring application context. That means using JUnit 5 and Mockito for most unit tests.

Use Spring’s test support only when you need Spring-specific behavior such as dependency injection, MVC request handling, configuration binding, or persistence integration.


1. Unit Test a Spring @Service

Example service:

package com.example.order;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    public Order createOrder(String customerEmail) {
        if (customerEmail == null || customerEmail.isBlank()) {
            throw new IllegalArgumentException("Customer email is required");
        }

        Order order = new Order(customerEmail);
        return orderRepository.save(order);
    }
}

Unit test:

package com.example.order;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    @InjectMocks
    private OrderService orderService;

    @Test
    void createOrderSavesOrder() {
        Order savedOrder = new Order("[email protected]");

        when(orderRepository.save(any(Order.class))).thenReturn(savedOrder);

        Order result = orderService.createOrder("[email protected]");

        assertEquals("[email protected]", result.getCustomerEmail());
        verify(orderRepository).save(any(Order.class));
    }

    @Test
    void createOrderRejectsBlankEmail() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> orderService.createOrder(" ")
        );

        assertEquals("Customer email is required", exception.getMessage());
    }
}

This is a true unit test because no Spring context is started.


2. Unit Test a Spring @Component

Example component:

package com.example.notification;

import org.springframework.stereotype.Component;

@Component
public class EmailValidator {

    public boolean isValid(String email) {
        return email != null && email.contains("@");
    }
}

Test:

package com.example.notification;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class EmailValidatorTest {

    private final EmailValidator emailValidator = new EmailValidator();

    @Test
    void returnsTrueForValidEmail() {
        assertTrue(emailValidator.isValid("[email protected]"));
    }

    @Test
    void returnsFalseForInvalidEmail() {
        assertFalse(emailValidator.isValid("invalid-email"));
        assertFalse(emailValidator.isValid(null));
    }
}

If a component has no dependencies, just instantiate it directly.


3. Unit Test a Spring MVC @Controller

For controllers, use @WebMvcTest. This loads only the MVC layer, not the whole application.

Example controller:

package com.example.order;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @GetMapping("/orders/{id}")
    public OrderResponse getOrder(@PathVariable Long id) {
        return orderService.getOrder(id);
    }
}

Controller test:

package com.example.order;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private OrderService orderService;

    @Test
    void getOrderReturnsOrder() throws Exception {
        when(orderService.getOrder(1L))
                .thenReturn(new OrderResponse(1L, "[email protected]"));

        mockMvc.perform(get("/orders/1"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1L))
                .andExpect(jsonPath("$.customerEmail").value("[email protected]"));
    }
}

In newer Spring Boot versions, prefer @MockitoBean over the older @MockBean.


4. Unit Test Repository-Using Services

If your service depends on a Spring Data JPA repository, mock the repository in a unit test.

package com.example.user;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void findUserReturnsUser() {
        User user = new User(1L, "[email protected]");

        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        User result = userService.findUser(1L);

        assertEquals("[email protected]", result.getEmail());
    }
}

Do not use a real database for a unit test. If you want to test repository mappings or queries, use an integration/slice test such as @DataJpaTest.


5. Test Spring Data JPA Repositories with @DataJpaTest

This is not a pure unit test, but it is the standard way to test repositories.

package com.example.user;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertTrue;

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmailReturnsUser() {
        User user = new User();
        user.setEmail("[email protected]");

        userRepository.save(user);

        Optional<User> result = userRepository.findByEmail("[email protected]");

        assertTrue(result.isPresent());
    }
}

Use this when you want to verify:

  • JPA mappings
  • repository query methods
  • custom JPQL/native queries
  • database constraints

6. Recommended Dependencies

For Maven, the common Spring Boot test starter is usually enough:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

It includes commonly used testing libraries such as:

  • JUnit Jupiter
  • AssertJ
  • Mockito
  • Spring Test
  • MockMvc support

7. Common Testing Patterns

Arrange, Act, Assert

@Test
void methodNameExpectedBehavior() {
    // Arrange
    when(repository.findById(1L)).thenReturn(Optional.of(entity));

    // Act
    Result result = service.doSomething(1L);

    // Assert
    assertEquals(expectedValue, result.value());
}

Verify interactions only when meaningful

verify(repository).save(any(Order.class));

Avoid verifying every single method call. Prefer verifying observable behavior.

Test exceptions

@Test
void throwsExceptionWhenUserNotFound() {
    assertThrows(
            UserNotFoundException.class,
            () -> userService.findUser(999L)
    );
}

8. Choosing the Right Test Type

Component Recommended test style
Plain utility/component Instantiate directly
@Service with dependencies JUnit 5 + Mockito
@Controller @WebMvcTest + MockMvc
Repository @DataJpaTest
Full application flow @SpringBootTest

Rule of Thumb

Use the smallest test scope that proves the behavior:

  • Business logic: plain JUnit + Mockito
  • Web layer: @WebMvcTest
  • Persistence layer: @DataJpaTest
  • End-to-end Spring wiring: @SpringBootTest

Most Spring component unit tests should not need @SpringBootTest.

Introduction to JUnit

In this post we will start to learn about JUnit Framework. JUnit is a framework for unit testing Java applications. It was developed by Kent Back and Erich Gamma to help developers to create a better applications. JUnit has become the standard tool used by developers when it comes to unit testing.

Every day, when you create an application from the smallest one, that consist of a single class to a project of a large application, your development workflow will always be the same. You will begin by writing a small code, compile it, run the code and finally test it. And you will mostly find that things doesn’t work as expected at first. So you’ll go back to your favorite IDE, check what the error was, and you will repeat the process, code, compile, run and test, until you get the result that you want.

We can do this steps manually. For example when you have a console application you will typically run the code, check the result printed out on the console screen to find out if the program return the correct result. When you find the error you will fix it and re-run the code again and again. This kind of test seems like a time-consuming activity, tiresome and even make you bored. And instead of testing the code you are testing your ability to test. Because you have to check whether the output printed in the console is correct or not.

Here is a simple example of a very simple calculator that know how to add two numbers and how you are testing it using a simple main class in a console application.

package org.kodejava.junit;

public class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int a = 10;
        int b = 15;
        int c = Calculator.add(a, b);

        System.out.println("a = " + a);
        System.out.println("b = " + b);
        System.out.println("c = " + c);

        if (c == 25) {
            System.out.println("Test success!");
        } else {
            System.out.println("Test failed!");
        }
    }
}
Testing your ability to test.

Testing your ability to test.

To make life easier JUnit framework comes with great help to overcome these problems. You can tell JUnit framework to test your code by creating a unit tests. You will create a class that test your code, you’ll define your testing criteria in this unit testing class. You can execute these unit testing, and it will report whether the tests pass or fail. And you can run it as many times as you’ll like to see if the test still pass after you modified parts of your code.

Now, let see how JUnit can help us to test the previous calculator code. Before creating the unit test class we need to download the JUnit library from the website at JUnit, follow the link to download page and download the jars. There are two main jar files that you need to download, the junit.jar and hamcrest-core.jar. When you are using and IDE, the JUnit usually installed as part of the IDE such as NetBeans, Eclipse or IntelliJ IDEA.

Let’s write the unit testing code.

package org.kodejava.junit;

import org.junit.Test;
import static org.junit.Assert.*;

public class CalculatorTest {
    @Test
    public void addTwoNumbersTest() {
        int a = 10;
        int b = 15;
        assertEquals(25, Calculator.add(a, b));
    }
}

From this code snippet what you can see is:

  • We create a class called CalculatorTest. We usually name the unit test class with the same name with the class under test but suffix it with Test.
  • We create a test method called addTwoNumberTest(). You can name your test method as descriptive as possible. This will have you understand what the method is trying to test.
  • To make a method a unit test method we add the @Test annotation to it. This annotation will tell the JUnit framework that the annotated method is a part of the unit testing it should execute on our behalf.
  • In the test method body we use the assertEquals() method to check the test result. In this case we expect 25 is returned when the Calculaor.add() method add two numbers, the a + b.

After you create the unit testing class, let’s compile it and this JUnit test from the command prompt. Remember to have also the class under test, which is the Calculator class beside your unit testing class. To execute JUnit test you can type the following command.

java -cp .;junit-4.13.2.jar org.junit.runner.JUnitCore org.kodejava.junit.CalculatorTest

I am intentionally place the junit.jar file in the root of my work directory so that I can make the -cp option short. The classpath options tell Java where to look the required class files.

Executing JUnit Test

Executing JUnit Test

That is all for the bit of introduction to JUnit. In the coming post I will write more about JUnit and how to use other available annotation, so you can write a better test.