Testing in Java: unit, integration, e2e — a complete guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Testing in Java: From Unit to End-to-End Tests

Quality testing is the foundation of reliable software. In the Java world, there is an established testing pyramid that includes three main levels: unit, integration, and e2e (end-to-end) tests. In this article, we will break down each of them, provide practical examples in Java, and explain how to properly build a testing strategy in your project.



1. Unit Testing: The Foundation of the Pyramid

Unit tests check individual components of a program in isolation — typically one class or method. They run quickly, do not require external dependencies (DB, API), and should cover most business logic.



Tools for Unit Tests in Java

  • JUnit 5 — standard framework for writing tests.
  • Mockito — library for creating mock objects.
  • AssertJ — fluent assertions for readable checks.


Example of a Unit Test with JUnit 5 and Mockito

Suppose we have an OrderService that depends on PaymentGateway. We want to test the discount calculation logic without calling the real payment gateway.



// Class under testpublic class OrderService {    private final PaymentGateway paymentGateway;

public OrderService(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; }

public double calculateDiscount(double amount) { if (amount > 1000) { return amount * 0.1; // 10% discount } return 0; }}

// Unit testimport 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;

@ExtendWith(MockitoExtension.class)public class OrderServiceTest {

@Mock private PaymentGateway paymentGateway;

@InjectMocks private OrderService orderService;

@Test public void testCalculateDiscount_AmountAbove1000_Returns10Percent() { double result = orderService.calculateDiscount(1500); assertEquals(150.0, result, 0.001); }

@Test public void testCalculateDiscount_AmountBelow1000_ReturnsZero() { double result = orderService.calculateDiscount(500); assertEquals(0.0, result); }}


Best practices: use @BeforeEach to initialize common data, avoid testing private methods directly, follow the "one assertion per test" rule.



2. Integration Testing: Checking Interaction

Integration tests check how multiple modules work together — for example, a service interacting with a database, message queues, or external APIs. Unlike unit tests, these use real or in-memory dependencies.



Tools for Integration Tests

  • Spring Boot Test — for testing Spring applications.
  • Testcontainers — for spinning up Docker containers (DB, Kafka).
  • H2 Database — in-memory database for fast tests.


Example of an Integration Test with Spring Boot

Testing a REST controller with a real database connection (using H2 in the test).



// Controller@RestController@RequestMapping("/api/users")public class UserController {    private final UserRepository userRepository;

public UserController(UserRepository userRepository) { this.userRepository = userRepository; }

@GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userRepository.findById(id) .orElseThrow(() -> new Runti

Blogs

Book Recommendations