In the realm of Spring Boot application development, ensuring the reliability and correctness of your service layer is paramount. Your service layer forms the backbone of your application, housing the core business logic that drives your software. To ensure it functions flawlessly, robust unit testing is essential. Welcome to our comprehensive guide, where we will delve deep into the world of “Spring Boot unit test for service layer.” We will equip you with the knowledge and tools needed to master the art of unit testing in this critical component of your Spring Boot applications.
“Spring Boot unit test for service layer” isn’t just about verifying that your services work as expected; it’s about crafting a comprehensive testing strategy that guarantees the resilience and robustness of your application. Throughout this guide, we’ll explore the intricacies of unit testing, including the use of powerful testing tools and libraries that Spring Boot offers. We’ll dive into best practices, discuss common challenges, and provide real-world examples to help you gain confidence in your service layer’s functionality.
By the end of this journey, you’ll have a solid understanding of how to create effective unit tests for your Spring Boot service layer, ensuring that your application not only meets its functional requirements but also stands up to the rigors of real-world usage. So, if you’re ready to elevate your Spring Boot development skills and produce services that are both dependable and maintainable, let’s embark on this comprehensive exploration of “Spring Boot unit test for service layer.”
This is the second part of the tutorial where we will learn about testing the business/service layer. If you miss Part 1, then you can check the previous post here. In the next article, we will learn about testing the controller layer. So by the end, we will learn how to write integration test cases and end to end cases. So let’s get started –
Read More:
- Check the Complete JUnit 5 Tutorial
- Check the Complete JavaServer Faces (JSF) Tutorial
- Check the Spring Boot JdbcTemplate Tutorials
- Check the Complete Spring Boot and Data JPA Tutorials
- Check the Complete Spring MVC Tutorials
- Check the Complete JSP Tutorials
- Check the Complete Spring Boot Tutorials [100+ Examples]
- Check the Complete Spring Boot and Thymeleaf Tutorial
- Check the Complete AWS Tutorial
- Check the Complete JavaServer Faces (JSF) Tutorial
- Check the Complete Spring Data JPA Tutorial
- Check the Complete Spring Security Tutorial
- Check the Javascript Projects for Beginners
Watch the video on Spring Boot Unit Test for Service Layer
Create Service/Business Layer
Let’s create a class MovieService.java inside in.bushansirgur.springbootjunit.service
and the following content
package in.bushansirgur.springbootjunit.service;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import in.bushansirgur.springbootjunit.model.Movie;
import in.bushansirgur.springbootjunit.repository.MovieRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class MovieService {
private final MovieRepository movieRepository;
public Movie save(Movie movie) {
return movieRepository.save(movie);
}
public List<Movie> getAllMovies() {
return movieRepository.findAll();
}
public Movie getMovieById(Long id) {
return movieRepository.findById(id).orElseThrow(() -> new RuntimeException("Movie found for the id "+id));
}
public Movie updateMovie(Movie movie, Long id) {
Movie existingMovie = movieRepository.findById(id).get();
existingMovie.setGenera(movie.getGenera());
existingMovie.setName(movie.getName());
existingMovie.setReleaseDate(movie.getReleaseDate());
return movieRepository.save(existingMovie);
}
public void deleteMovie(Long id) {
Movie existingMovie = movieRepository.findById(id).get();
movieRepository.delete(existingMovie);
}
}
Create JUnit Class
Now we need to write JUnit test cases for the above service class. In order to test the Service layer, we will also use Mockito. It is a testing framework, it will help us to mock the objects and we can stub the methods. It means that we will not test the real implementation, instead we will provide a fake implementation. Let’s create MovieServiceTest.class
inside src/test/java
and add the following content
package in.bushansirgur.springbootjunit.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
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 in.bushansirgur.springbootjunit.model.Movie;
import in.bushansirgur.springbootjunit.repository.MovieRepository;
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
@Mock
private MovieRepository movieRepository;
@InjectMocks
private MovieService movieService;
@Test
void save() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
when(movieRepository.save(any(Movie.class))).thenReturn(avatarMovie);
Movie newMovie = movieService.save(avatarMovie);
assertNotNull(newMovie);
assertThat(newMovie.getName()).isEqualTo("Avatar");
}
@Test
void getMovies() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
Movie titanicMovie = new Movie();
titanicMovie.setId(2L);
titanicMovie.setName("Titanic");
titanicMovie.setGenera("Romance");
titanicMovie.setReleaseDate(LocalDate.of(2004, Month.JANUARY, 10));
List<Movie> list = new ArrayList<>();
list.add(avatarMovie);
list.add(titanicMovie);
when(movieRepository.findAll()).thenReturn(list);
List<Movie> movies = movieService.getAllMovies();
assertEquals(2, movies.size());
assertNotNull(movies);
}
@Test
void getMovieById() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
Movie existingMovie = movieService.getMovieById(avatarMovie.getId());
assertNotNull(existingMovie);
assertThat(existingMovie.getId()).isNotEqualTo(null);
}
@Test
void getMovieByIdForException() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
when(movieRepository.findById(2L)).thenReturn(Optional.of(avatarMovie));
assertThrows(RuntimeException.class, () -> {
movieService.getMovieById(avatarMovie.getId());
});
}
@Test
void updateMovie() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
when(movieRepository.save(any(Movie.class))).thenReturn(avatarMovie);
avatarMovie.setGenera("Fantacy");
Movie exisitingMovie = movieService.updateMovie(avatarMovie, avatarMovie.getId());
assertNotNull(exisitingMovie);
assertEquals("Fantacy", avatarMovie.getGenera());
}
@Test
void deleteMovie() {
Movie avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
Long movieId = 1L;
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
doNothing().when(movieRepository).delete(any(Movie.class));
movieService.deleteMovie(movieId);
verify(movieRepository, times(1)).delete(avatarMovie);
}
}
Now when we run the test class, we should see the green bar
Let’s discuss the few annotations that we used above –
@Mock
marks the fields as mock objects. At the core, it is used to mock the objects that helps in minimizing the repetitive mock objects.@InjectMocks
injects mock objects to the marked fields, but the marked fields are not mocks.
Now, let’s look at the syntax for stub a method:
when(mock.method()).thenReturn(someValue)
Here is the order that this code will execute in:
mock.method()
when(<result of step 1>)
<result of step 2>.thenReturn
When the when
method is invoked after the invocation of method()
, it delegates to MockitoCore.when
, which calls the stub()
method of the same class. This method unpacks the ongoing stubbing from the shared MockingProgress
instance that the mocked method()
invocation wrote into, and returns it. Then thenReturn
method is then called on the OngoingStubbing
instance.
Now let’s refactor the above test class by using JUnit life cycle method so that each time when we execute a test case, instead of setting up the data inside each test case, we can setup outside by using @BeforeEach
annotation. This will execute the method every time before executing each test case.
package in.bushansirgur.springbootjunit.service;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
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 in.bushansirgur.springbootjunit.model.Movie;
import in.bushansirgur.springbootjunit.repository.MovieRepository;
@ExtendWith(MockitoExtension.class)
public class MovieServiceTest {
@Mock
private MovieRepository movieRepository;
@InjectMocks
private MovieService movieService;
private Movie avatarMovie;
private Movie titanicMovie;
@BeforeEach
void init() {
avatarMovie = new Movie();
avatarMovie.setId(1L);
avatarMovie.setName("Avatar");
avatarMovie.setGenera("Action");
avatarMovie.setReleaseDate(LocalDate.of(2000, Month.APRIL, 23));
titanicMovie = new Movie();
titanicMovie.setId(2L);
titanicMovie.setName("Titanic");
titanicMovie.setGenera("Romance");
titanicMovie.setReleaseDate(LocalDate.of(2004, Month.JANUARY, 10));
}
@Test
void save() {
when(movieRepository.save(any(Movie.class))).thenReturn(avatarMovie);
Movie newMovie = movieService.save(avatarMovie);
assertNotNull(newMovie);
assertThat(newMovie.getName()).isEqualTo("Avatar");
}
@Test
void getMovies() {
List<Movie> list = new ArrayList<>();
list.add(avatarMovie);
list.add(titanicMovie);
when(movieRepository.findAll()).thenReturn(list);
List<Movie> movies = movieService.getAllMovies();
assertEquals(2, movies.size());
assertNotNull(movies);
}
@Test
void getMovieById() {
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
Movie existingMovie = movieService.getMovieById(avatarMovie.getId());
assertNotNull(existingMovie);
assertThat(existingMovie.getId()).isNotEqualTo(null);
}
@Test
void getMovieByIdForException() {
when(movieRepository.findById(2L)).thenReturn(Optional.of(avatarMovie));
assertThrows(RuntimeException.class, () -> {
movieService.getMovieById(avatarMovie.getId());
});
}
@Test
void updateMovie() {
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
when(movieRepository.save(any(Movie.class))).thenReturn(avatarMovie);
avatarMovie.setGenera("Fantacy");
Movie exisitingMovie = movieService.updateMovie(avatarMovie, avatarMovie.getId());
assertNotNull(exisitingMovie);
assertEquals("Fantacy", avatarMovie.getGenera());
}
@Test
void deleteMovie() {
Long movieId = 1L;
when(movieRepository.findById(anyLong())).thenReturn(Optional.of(avatarMovie));
doNothing().when(movieRepository).delete(any(Movie.class));
movieService.deleteMovie(movieId);
verify(movieRepository, times(1)).delete(avatarMovie);
}
}
Now when we run this JUnit class, we should see the green bar. All the test cases should pass as expected.
That’s it for this article, in the next article we will continue with the same application where we will write the JUnit test cases for other layers as well. Thank you so much for reading this post, if you feel this post helped you then consider sharing this with your friends, colleagues and social media.