I'm new to Spring Boot and I'm trying to understand concept of testing. Recently I was trying to implement DELETE request test for my controller, but I ran into some problems, and I have some quiestions:
Firstly, how can I test a Delete, PUT or GET single object request if there isn't any object to delete? Should I initiate some object at each start of the test?
Secondly, what's the point of injecting Service in such tests? We are mocking it, and that's all. I didn't find out by myself any usage of service in testing. Maybe there is a way to solve the first problem using it?
There is my testing class
package com.example.springrestuser;
import com.example.springrestuser.user.controller.UserController;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.security.Sha256;
import com.example.springrestuser.user.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
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.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.time.LocalDate;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void canAddUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("[email protected]")
.build();
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated());
}
@Test
public void canDeleteUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("[email protected]")
.build();
userService.add(user); //maybe it would work somehow?
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canPutUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("[email protected]")
.build();
//sending the post method and then put?
mvc.perform(MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(user)));
user.setName("new_User");
mvc.perform(MockMvcRequestBuilders.put("/api/users/{login}","user"))
.andExpect(status().isAccepted());
}
@Test
public void canGetSingleUserTest() throws Exception {
User user = User.builder()
.login("user")
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("[email protected]")
.build();
mvc.perform(MockMvcRequestBuilders.get("/api/users/{login}", "adam"))
.andExpect(status().isOk());
}
}
There is the controller class
package com.example.springrestuser.user.controller;
import com.example.springrestuser.user.dto.CreateUserRequest;
import com.example.springrestuser.user.dto.GetUserResponse;
import com.example.springrestuser.user.dto.GetUsersResponse;
import com.example.springrestuser.user.dto.UpdateUserRequest;
import com.example.springrestuser.user.entity.User;
import com.example.springrestuser.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("api/users")
public class UserController {
UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("")
public ResponseEntity<GetUsersResponse> getUsers() {
List<User> all = userService.findAll();
GetUsersResponse getUsersResponse = GetUsersResponse.entityToDtoMapper(all);
return ResponseEntity.ok(getUsersResponse);
}
@GetMapping("{login}")
public ResponseEntity<GetUserResponse> getUser(@PathVariable("login") String login) {
return userService.find(login)
.map(user -> ResponseEntity.ok(GetUserResponse.entityToDtoMapper(user)))
.orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping("")
public ResponseEntity<Void> createUser(@RequestBody CreateUserRequest request) {
User user = CreateUserRequest.dtoToEntityMapper(request);
userService.add(user);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@PutMapping("{login}")
public ResponseEntity<Void> updateUser(@RequestBody UpdateUserRequest request,
@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
UpdateUserRequest.dtoToEntityMapper(request, user.get());
userService.update(user.get());
return ResponseEntity.accepted().build();
}
@DeleteMapping("{login}")
public ResponseEntity<Void> deleteUser(@PathVariable("login") String login) {
Optional<User> user = userService.find(login);
if (user.isEmpty()) {
return ResponseEntity.notFound().build();
}
userService.delete(user.get());
return ResponseEntity.accepted().build();
}
}
CodePudding user response:
Answering your questions:
- With
@WebMvcTest
you are performing a test to your Controller layer without the rest of the application being loaded (usually called a Slice Test). This means that all you are going to test here is the logic in your Controller in addition to the serialization and deserialization of JSON. This means that you are not actually deleting anything because you are only testing your Controller layer. To actually test the deletion of something in your database you would need to do a fully-fledged Integration Test. - Without a mocked
UserService
bean, yourUserController
would be instantiated by Spring. What you should do in this@WebMvcTest
is to mockUserService
according to the logic you want to test in the Controller. As an example, in your@DeleteMapping("{login}")
endpoint you would need to do the following tests:
- Test #1: mock
userService.find(login)
to return an emptyOptional<User>
and then check that the HTTP Status Code is 404. - Test #2: mock
userService.find(login)
to return a non-emptyOptional<User>
and then check that the HTTP Status Code is 202.
Something along the following lines:
@ExtendWith(SpringExtension.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private UserService userService;
@Test
public void shouldDeleteUserIfFound() throws Exception {
// Arrange
String login = "user";
User user = User.builder()
.login(login)
.name("User")
.surname("uuser")
.dateOfBirth(LocalDate.of(2000, 12, 4))
.password(Sha256.hash("useruser"))
.email("[email protected]")
.build();
doReturn(Optional.of(user)).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isAccepted());
}
@Test
public void shouldNotDeleteUserIfNotFound() throws Exception {
// Arrange
String login = "user";
doReturn(Optional.empty()).when(this.userService).find(login);
// Act & Assert
mvc.perform(MockMvcRequestBuilders.delete("/api/users/{login}", login))
.andExpect(status().isNotFound());
}
}