In this article, we will show you how to test various layers of Spring Boot 2 application. We will use Order management application created in previous post ‘Create a REST API with Spring Boot‘ for this article. Main technologies used to unit test Spring Boot application are JUnit 5, Mockito, DataJpaTest and WebMvcTest.
Maven Test dependencies
<!--Spring Boot test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> </exclusions> </dependency> <!--Junit 5--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> <!-- In Memory database useful for testing--> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
Layers to be Unit tested
- Controller Layer
- Service Layer
- Repository Layer
Testing Controller Layer
The aim of controller Test is to test controller in isolation while mocking dependent services.
UserControllerTest
@WebMvcTest(value = UserController.class) class UserControllerTest { @MockBean private UserService userService; @Autowired private MockMvc mockMvc; private User user; private User user2; @Autowired private ObjectMapper objectMapper; @BeforeEach void setup() { user = new User(); user.setFirstName("Steve"); user.setLastName("Rob"); user.setEmail("steverob@gmail.com"); user.setId(1L); user.setFirstLineOfAddress("12 Avenue"); user.setSecondLineOfAddress("Commercial street"); user.setTown("London"); user.setPostCode("HA6 0EW"); user2 = new User(); user2.setFirstName("Stev"); user2.setLastName("Ro"); user2.setEmail("stevob@gmail.com"); user2.setId(2L); user2.setFirstLineOfAddress("13 Avenue"); user2.setSecondLineOfAddress("Commercial street"); user2.setTown("London"); user2.setPostCode("HA7 0EW"); } @Test void testGetUser() throws Exception { String expected = objectMapper.writeValueAsString(user); when(userService.getUserById(anyLong())).thenReturn(user); RequestBuilder requestBuilder = MockMvcRequestBuilders.get( "/api/users/" + user.getId()).accept( MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); assertEquals(expected, mvcResult.getResponse().getContentAsString()); } @Test public void testGetAllUsers() throws Exception { List<User> users = Arrays.asList(user, user2); String expected = objectMapper.writeValueAsString(users); when(userService.getUsers()).thenReturn(users); RequestBuilder requestBuilder = MockMvcRequestBuilders.get( "/api/users").accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); assertEquals(expected, mvcResult.getResponse().getContentAsString()); } @Test void testCreateUser() throws Exception { String expected = objectMapper.writeValueAsString(user); when(userService.createUser(any(User.class))).thenReturn(user); RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/api/users") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(user)) .accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isCreated()) .andReturn(); assertEquals(expected, mvcResult.getResponse().getContentAsString()); } @Test void testUpdateUser() throws Exception { User updateUser = new User(); updateUser.setFirstName("Steveupdated"); updateUser.setLastName("Robupdated"); updateUser.setEmail("steverobupdated@gmail.com"); updateUser.setFirstLineOfAddress("12 Avenueupdated"); updateUser.setSecondLineOfAddress("Commercial streetupdated"); updateUser.setTown("London"); updateUser.setPostCode("HA6 0EW"); User updateUserWithId = new User(); new ModelMapper().map(updateUser, updateUserWithId); updateUserWithId.setId(1L); //Exclude null values objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String putBody = objectMapper.writeValueAsString(updateUser); String expectedResponse = objectMapper.writeValueAsString(updateUserWithId); when(userService.updateUser(anyLong(), any(User.class))).thenReturn(updateUserWithId); RequestBuilder requestBuilder = MockMvcRequestBuilders.put("/api/users/{userId}", updateUserWithId.getId()) .contentType(MediaType.APPLICATION_JSON) .content(putBody) .accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); assertEquals(expectedResponse, mvcResult.getResponse().getContentAsString()); } @Test void testPatchUpdateUser() throws Exception { User updateUser = new User(); updateUser.setFirstName("Steveupdated"); updateUser.setLastName("Robupdated"); user.setFirstName(updateUser.getFirstName()); user.setLastName(updateUser.getLastName()); //Exclude null values objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String patchBody = objectMapper.writeValueAsString(updateUser); String expectedResponse = objectMapper.writeValueAsString(user); when(userService.patchUpdateUser(anyLong(), any(User.class))).thenReturn(user); RequestBuilder requestBuilder = MockMvcRequestBuilders.patch("/api/users/{userId}", user.getId()) .contentType(MediaType.APPLICATION_JSON) .content(patchBody) .accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); assertEquals(expectedResponse, mvcResult.getResponse().getContentAsString()); } @Test void testDeleteUser() throws Exception { doNothing().when(userService).deleteUser(anyLong()); RequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/api/users/" + user.getId()) .accept(MediaType.APPLICATION_JSON); MvcResult mvcResult = mockMvc.perform(requestBuilder) .andExpect(MockMvcResultMatchers.status().isNoContent()) .andReturn(); assertEquals("", mvcResult.getResponse().getContentAsString()); } }
@WebMvcTest(value = UserController.class): This tells Spring Boot to fire up application context with only beans required for this controller.
@MockBean: Automatically replaces bean with mockito mock. For unit testing we don’t need real services hence using mock objects.
@Autowired: Automatically create real instance into the concerned class.
@BeforeEach: Setup method triggered before each test
@Test: Indicates JUnit test method
objectMapper.writeValueAsString(user): Used to convert object to Json string
when(userService.getUserById(anyLong())).thenReturn(user): Configures mocked services to returns predefined object when mocked object’s method is called.
RequestBuilder requestBuilder = MockMvcRequestBuilders.post(“/api/users”) : Creates request builder with body content and headers and uses the same to perform HTTP request. Also verifies expected status code.
assertEquals(expected, mvcResult.getResponse().getContentAsString()): verifies response body
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String patchBody = objectMapper.writeValueAsString(updateUser); :
Above two statements says that only non null values of updateUser object should be used for serialisation as we don’t want null property values in request body.
Rest of the code is self explanatory.
Testing Service Layer
The aim of Service Test is to test service in isolation while mocking dependent repository
UserServiceImplTest
@ExtendWith(SpringExtension.class) class UserServiceImplTest { @InjectMocks private UserServiceImpl userService; @Mock private UserRepository userRepository; private User user; private User user2; @BeforeEach void setUp() { MockitoAnnotations.initMocks(this); user = new User(); user.setId(1L); user.setFirstName("Steve"); user.setLastName("Rob"); user.setEmail("steverob@gmail.com"); user.setFirstLineOfAddress("10 Oliver"); user.setSecondLineOfAddress("Commercial street"); user.setTown("London"); user.setPostCode("HA4 7EW"); user2 = new User(); user2.setId(2L); user2.setFirstName("Tony"); user2.setLastName("Taylor"); user2.setEmail("tonytaylor@gmail.com"); user2.setFirstLineOfAddress("12 Avenue"); user2.setSecondLineOfAddress("Avenue street"); user2.setTown("London"); user2.setPostCode("HA6 0EW"); } @Test void testGetUserById() { when(userRepository.findById(anyLong())).thenReturn(Optional.of(user)); User userFound = userService.getUserById(user.getId()); assertNotNull(userFound); assertEquals(user.getId(), userFound.getId()); assertEquals(user.getEmail(), userFound.getEmail()); assertEquals(user.getFirstName(), userFound.getFirstName()); assertEquals(user.getLastName(), userFound.getLastName()); assertEquals(user.getPostCode(), userFound.getPostCode()); assertEquals(user.getFirstLineOfAddress(), userFound.getFirstLineOfAddress()); assertEquals(user.getSecondLineOfAddress(), userFound.getSecondLineOfAddress()); assertEquals(user.getTown(), userFound.getTown()); } @Test void shouldThrowExceptionWhenGetUserHasWrongId() { when(userRepository.findById(anyLong())).thenReturn(Optional.empty()); assertThrows(RuntimeException.class, () -> userService.getUserById(10000L)); } @Test void testGetUsers() { List<User> users = Arrays.asList(user, user2); when(userRepository.findAll()).thenReturn(users); List<User> usersReceived = userService.getUsers(); assertEquals(users, usersReceived); assertEquals(users.size(), usersReceived.size()); assertEquals(users.get(0).getId(), usersReceived.get(0).getId()); assertEquals(users.get(0).getFirstName(), usersReceived.get(0).getFirstName()); assertEquals(users.get(0).getLastName(), usersReceived.get(0).getLastName()); assertEquals(users.get(0).getEmail(), usersReceived.get(0).getEmail()); assertEquals(users.get(0).getFirstLineOfAddress(), usersReceived.get(0).getFirstLineOfAddress()); assertEquals(users.get(0).getSecondLineOfAddress(), usersReceived.get(0).getSecondLineOfAddress()); assertEquals(users.get(0).getSecondLineOfAddress(), usersReceived.get(0).getSecondLineOfAddress()); } @Test void testCreateUser() { when(userRepository.findByEmail(anyString())).thenReturn(Optional.empty()); when(userRepository.save(any(User.class))).thenReturn(user); User createNewUser = new User(); createNewUser.setFirstName(user.getFirstName()); createNewUser.setLastName(user.getLastName()); createNewUser.setEmail(user.getEmail()); createNewUser.setPostCode(user.getPostCode()); createNewUser.setFirstLineOfAddress(user.getFirstLineOfAddress()); createNewUser.setSecondLineOfAddress(user.getSecondLineOfAddress()); createNewUser.setTown(user.getTown()); User createdUser = userService.createUser(createNewUser); assertNotNull(createdUser); assertEquals(user.getId(), createdUser.getId()); assertEquals(user.getEmail(), createdUser.getEmail()); assertEquals(user.getFirstName(), createdUser.getFirstName()); assertEquals(user.getLastName(), createdUser.getLastName()); assertEquals(user.getPostCode(), createdUser.getPostCode()); assertEquals(user.getFirstLineOfAddress(), createdUser.getFirstLineOfAddress()); assertEquals(user.getSecondLineOfAddress(), createdUser.getSecondLineOfAddress()); assertEquals(user.getTown(), createdUser.getTown()); } @Test void testUpdateUser() { User updateUser = new User(); updateUser.setFirstName("Steve"); updateUser.setLastName("Rob"); updateUser.setEmail("steverob@gmail.com"); updateUser.setPostCode("HA5 2EW"); updateUser.setFirstLineOfAddress("10 Oliver"); updateUser.setSecondLineOfAddress("Commercial street"); updateUser.setTown("London"); User updateUserWithId = new User(); new ModelMapper().map(updateUser, updateUserWithId); updateUserWithId.setId(1L); when(userRepository.findById(anyLong())).thenReturn(Optional.of(updateUserWithId)); when(userRepository.save(any(User.class))).thenReturn(updateUserWithId); User savedUser = userService.updateUser(updateUserWithId.getId(), updateUser); assertNotNull(savedUser); assertEquals(updateUserWithId.getId(), savedUser.getId()); assertEquals(updateUserWithId.getEmail(), savedUser.getEmail()); assertEquals(updateUserWithId.getFirstName(), savedUser.getFirstName()); assertEquals(updateUserWithId.getLastName(), savedUser.getLastName()); assertEquals(updateUserWithId.getPostCode(), savedUser.getPostCode()); assertEquals(updateUserWithId.getFirstLineOfAddress(), savedUser.getFirstLineOfAddress()); assertEquals(updateUserWithId.getSecondLineOfAddress(), savedUser.getSecondLineOfAddress()); assertEquals(updateUserWithId.getTown(), savedUser.getTown()); } @Test void testPatchUpdateUser() { User updateUser = new User(); updateUser.setFirstName("Steveupdated"); updateUser.setLastName("Robupdated"); user.setFirstName(updateUser.getFirstName()); user.setLastName(updateUser.getLastName()); when(userRepository.findById(anyLong())).thenReturn(Optional.of(user)); when(userRepository.save(any(User.class))).thenReturn(user); User savedUser = userService.patchUpdateUser(user.getId(), updateUser); assertNotNull(savedUser); assertEquals(user.getId(), savedUser.getId()); assertEquals(user.getEmail(), savedUser.getEmail()); assertEquals(user.getFirstName(), savedUser.getFirstName()); assertEquals(user.getLastName(), savedUser.getLastName()); assertEquals(user.getPostCode(), savedUser.getPostCode()); assertEquals(user.getFirstLineOfAddress(), savedUser.getFirstLineOfAddress()); assertEquals(user.getSecondLineOfAddress(), savedUser.getSecondLineOfAddress()); assertEquals(user.getTown(), savedUser.getTown()); } @Test void testDeleteUser() { when(userRepository.findById(anyLong())).thenReturn(Optional.of(user)); doNothing().when(userRepository).delete(any(User.class)); userService.deleteUser(user.getId()); verify(userRepository, times(1)).findById(user.getId()); verify(userRepository, times(1)).delete(user); } }
@ExtendWith(SpringExtension.class):Integrates Spring TestContext with JUnit 5 Test. This annotation is not required when @DataJpaTest, @WebMvcTest, and @SpringBootTest are used, as it already included.
@InjectMocks: Indicates mocked object will be injected.
@Mock: Indicates mock object will be used instead of real objects. MockitoAnnotations.initMocks(this): Initiates all mocks in the class.
Rest of the code is self explanatory.
Testing Repository Layer
The aim of Repository Test is to test repository in isolation. In memory database is used instead of real database.
UserRepositoryTest
@DataJpaTest class UserRepositoryTest { @Autowired private UserRepository userRepository; @Autowired private TestEntityManager entityManager; @Test void testFndByEmail() { //Given User user = new User(); user.setFirstName("Steve"); user.setLastName("Rob"); user.setEmail("steverob@gmail.com"); user.setFirstLineOfAddress("10 Oliver"); user.setSecondLineOfAddress("Commercial street"); user.setTown("London"); user.setPostCode("HA4 7EW"); entityManager.persist(user); entityManager.flush(); //When User found = userRepository.findByEmail(user.getEmail()).get(); //Then assertEquals(user.getEmail(), found.getEmail()); } }
@DataJpaTest: Sets up H2, in memory database against which all queries will be run. It sets up Hibernate, Spring Data and data source.
entityManager.persist(user); entityManager.flush(): Creates data in the H2 database
Run all tests
mvn test
Complete Source code including tests location
https://github.com/ranjesh1/spring-boot2-rest-order-services.git