Saturday, 18 October 2025

 JUnit Testing in Spring Boot – A Complete Guide (Controller, Service, Repository)

Testing is one of the most important parts of building reliable applications. In Spring Boot, we usually test applications at different layers: Controller, Service, and Repository. Each layer has its own testing strategy.

In this blog, we’ll build a simple example step by step and write JUnit tests for each layer.


πŸ“‚ Example Project – User Management

We will use a simple User entity with the following layers:

  • Controller → Handles HTTP requests

  • Service → Contains business logic

  • Repository → Interacts with the database


🟒 1. Entity & DTO

// User.java (Entity/DTO) public class User { private String id; private String name; // Constructors public User(String id, String name) { this.id = id; this.name = name; } // Getters & Setters public String getId() { return id; } public String getName() { return name; } }

🟒 2. Repository

// UserRepository.java import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, String> { }

🟒 3. Service

// UserService.java import org.springframework.stereotype.Service; import java.util.Optional; @Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User getUser(String id) { return userRepository.findById(id).orElse(null); } public User saveUser(User user) { return userRepository.save(user); } }

🟒 4. Controller

// UserController.java import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") public User getUser(@PathVariable String id) { return userService.getUser(id); } @PostMapping public User createUser(@RequestBody User user) { return userService.saveUser(user); } }

πŸ§ͺ Writing Tests

Now let’s write JUnit tests for each layer.


πŸ”Ή 1. Service Layer Test

Here we test the business logic in UserService.
We don’t want to hit the database → so we mock the repository using @Mock and inject it into the service with @InjectMocks.

// UserServiceTest.java import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void testGetUser() { // Arrange User mockUser = new User("101", "Aamir"); when(userRepository.findById("101")).thenReturn(Optional.of(mockUser)); // Act User result = userService.getUser("101"); // Assert assertThat(result.getName()).isEqualTo("Aamir"); } @Test void testSaveUser() { User user = new User("102", "John"); when(userRepository.save(user)).thenReturn(user); User saved = userService.saveUser(user); assertThat(saved.getId()).isEqualTo("102"); } }

πŸ”Ή 2. Controller Layer Test

Here we want to test the HTTP endpoints without starting the full application.
We use:

  • @WebMvcTest(UserController.class) → Loads only Controller + MVC beans

  • MockMvc → To simulate HTTP requests

  • @MockBean → To mock the service layer inside Spring Context

// UserControllerTest.java 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.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; 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.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void testGetUser() throws Exception { User mockUser = new User("101", "Aamir"); when(userService.getUser("101")).thenReturn(mockUser); mockMvc.perform(get("/users/101") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Aamir")); } @Test void testCreateUser() throws Exception { User user = new User("102", "John"); when(userService.saveUser(Mockito.any(User.class))).thenReturn(user); mockMvc.perform(post("/users") .contentType(MediaType.APPLICATION_JSON) .content("{\"id\":\"102\",\"name\":\"John\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value("102")) .andExpect(jsonPath("$.name").value("John")); } }

πŸ”Ή 3. Repository Layer Test

Here we want to test the database interaction.
We use:

  • @DataJpaTest → Loads only JPA-related beans with an in-memory H2 database

  • Tests run against a temporary DB (no need to start the full application)

// UserRepositoryTest.java 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.assertj.core.api.Assertions.assertThat; @DataJpaTest class UserRepositoryTest { @Autowired private UserRepository userRepository; @Test void testSaveAndFindUser() { User user = new User("201", "Alice"); userRepository.save(user); Optional<User> found = userRepository.findById("201"); assertThat(found).isPresent(); assertThat(found.get().getName()).isEqualTo("Alice"); } }

πŸ“Š Summary

LayerAnnotations UsedPurpose
Service@ExtendWith(MockitoExtension.class), @Mock, @InjectMocksTest business logic with mocked dependencies
Controller@WebMvcTest, @MockBean, MockMvcTest REST endpoints without starting server
Repository@DataJpaTestTest JPA queries with in-memory DB

✅ Conclusion

  • Service Layer → Focus on business logic, mock dependencies.

  • Controller Layer → Focus on API endpoints, mock the service.

  • Repository Layer → Focus on DB interaction using in-memory DB.

By following this layered approach, you ensure that each part of your Spring Boot application is properly tested in isolation.

No comments:

Post a Comment