Testing is an essential part of developing robust and maintainable Spring Boot applications. This guide walks you through writing unit and integration tests using JUnit 5 and Mockito, two of the most popular tools in the Java ecosystem.
Why Use JUnit and Mockito?
- JUnit 5: Modern and flexible testing framework for Java.
- Mockito: A powerful mocking library that allows you to isolate units of code and simulate dependencies.
Together, they enable fast, isolated, and automated testing of your application’s business logic.
Maven Dependencies
Add the following to your pom.xml
:
<dependencies>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Optional: If you want to use Mockito explicitly -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
spring-boot-starter-test
already includes JUnit 5, Mockito, Hamcrest, and AssertJ.
Sample Spring Boot Application
Let’s say we’re building a simple service that manages books.
Book.java
public class Book {
private Long id;
private String title;
private String author;
// Constructors, Getters, Setters
}
BookRepository.java
@Repository
public class BookRepository {
private final Map<Long, Book> books = new HashMap<>();
public Book save(Book book) {
books.put(book.getId(), book);
return book;
}
public Optional<Book> findById(Long id) {
return Optional.ofNullable(books.get(id));
}
}
BookService.java
@Service
public class BookService {
private final BookRepository repository;
public BookService(BookRepository repository) {
this.repository = repository;
}
public Book createBook(Book book) {
return repository.save(book);
}
public Book getBook(Long id) {
return repository.findById(id)
.orElseThrow(() -> new RuntimeException("Book not found"));
}
}
Writing Unit Tests with JUnit and Mockito
Let’s write a unit test for the BookService
.
BookServiceTest.java
@ExtendWith(MockitoExtension.class)
public class BookServiceTest {
@Mock
private BookRepository bookRepository;
@InjectMocks
private BookService bookService;
@Test
void testCreateBook() {
Book book = new Book(1L, "1984", "George Orwell");
when(bookRepository.save(book)).thenReturn(book);
Book result = bookService.createBook(book);
assertNotNull(result);
assertEquals("1984", result.getTitle());
verify(bookRepository, times(1)).save(book);
}
@Test
void testGetBookFound() {
Book book = new Book(1L, "Brave New World", "Aldous Huxley");
when(bookRepository.findById(1L)).thenReturn(Optional.of(book));
Book result = bookService.getBook(1L);
assertEquals("Brave New World", result.getTitle());
}
@Test
void testGetBookNotFound() {
when(bookRepository.findById(2L)).thenReturn(Optional.empty());
RuntimeException exception = assertThrows(RuntimeException.class, () -> {
bookService.getBook(2L);
});
assertEquals("Book not found", exception.getMessage());
}
}
Integration Testing with Spring Context
For integration tests, load the Spring context and test actual beans.
BookServiceIntegrationTest.java
@SpringBootTest
public class BookServiceIntegrationTest {
@Autowired
private BookService bookService;
@Autowired
private BookRepository bookRepository;
@Test
void testIntegrationCreateAndFetchBook() {
Book book = new Book(100L, "Clean Code", "Robert C. Martin");
bookService.createBook(book);
Book fetched = bookService.getBook(100L);
assertEquals("Clean Code", fetched.getTitle());
}
}
Best Practices
- Mock external dependencies in unit tests.
- Use
@SpringBootTest
only when testing full application context. - Separate unit and integration tests in different source folders (
/test/unit
vs/test/integration
). - Test expected exceptions with
assertThrows
. - Use descriptive method names (e.g.,
testGetBookNotFound_ThrowsException
).
Illustration
Let me know if you’d like a visual diagram showing the flow between repository, service, and tests (JUnit + Mockito logos included).
Conclusion
Using JUnit and Mockito with Spring Boot makes testing efficient, readable, and maintainable. With the help of @Mock
, @InjectMocks
, and @SpringBootTest
, you can test every layer of your application with precision.