Home > front end >  How to test DELETE, PUT, GET single object requests in Spring Boot?
How to test DELETE, PUT, GET single object requests in Spring Boot?

Time:12-16

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:

  1. 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.
  2. Without a mocked UserService bean, your UserController would be instantiated by Spring. What you should do in this @WebMvcTest is to mock UserService 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 empty Optional<User> and then check that the HTTP Status Code is 404.
  • Test #2: mock userService.find(login) to return a non-empty Optional<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());
    }
}
  • Related