반응형

스프링부트에서 파일과 데이터를 업로드하고, 테스트하는 방법에 대해서 간략히 정리해보겠습니다.

단순히 컨트롤러 단에서 요청을 어떻게 받고, 어떻게 테스트할 것인지에 대해서만 살펴보겠습니다.

 

 

이에 대한 예시로, 게시글 생성 API를 작성해보겠습니다.

게시글을 작성하면서 여러 건의 이미지를 함께 첨부할 수 있어야합니다.

@RestController
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;

    @PostMapping("/api/posts")
    @ResponseStatus(HttpStatus.CREATED)
    public Response create(@Valid @ModelAttribute PostCreateRequest req) {
        return Response.success(postService.create(req));
    }
}

해당 API는 multipart/form-data에 대한 요청을 받고, 제약 조건을 검증할 수 있도록,

@Valid와 @ModelAttribute 어노테이션을 지정해줍니다.

 

 

PostCreateRequest를 살펴봅시다.

package kukekyakya.kukemarket.dto.post;

import ...

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PostCreateRequest {

    @NotBlank(message = "게시글 제목을 입력해주세요.")
    private String title;

    @NotBlank(message = "게시글 본문을 입력해주세요.")
    private String content;

    @NotNull(message = "가격을 입력해주세요.")
    @PositiveOrZero(message = "0원 이상을 입력해주세요")
    private Long price;

    @NotNull(message = "사용자 아이디를 입력해주세요.")
    @PositiveOrZero(message = "올바른 사용자 아이디를 입력해주세요.")
    private Long memberId;

    @NotNull(message = "카테고리 아이디를 입력해주세요.")
    @PositiveOrZero(message = "올바른 카테고리 아이디를 입력해주세요.")
    private Long categoryId;

    private List<MultipartFile> images = new ArrayList<>();
}

게시글에 대한 데이터와 MultipartFile로 파일을 함께 전달받습니다.

 

 

이제 multipart/form-data에 대한 요청을 정상적으로 받을 수 있는지 컨트롤러를 테스트해보겠습니다.

package kukekyakya.kukemarket.controller.post;

import kukekyakya.kukemarket.dto.post.PostCreateRequest;
import kukekyakya.kukemarket.service.post.PostService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static kukekyakya.kukemarket.factory.dto.PostCreateRequestFactory.createPostCreateRequestWithImages;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(MockitoExtension.class)
class PostControllerTest {
    @InjectMocks PostController postController;
    @Mock PostService postService;
    MockMvc mockMvc;

    @BeforeEach
    void beforeEach() {
        mockMvc = MockMvcBuilders.standaloneSetup(postController).build();
    }

    @Test
    void createTest() throws Exception{
        // given
        // 1
        ArgumentCaptor<PostCreateRequest> postCreateRequestArgumentCaptor = ArgumentCaptor.forClass(PostCreateRequest.class);

        List<MultipartFile> imageFiles = List.of(
                new MockMultipartFile("test1", "test1.PNG", MediaType.IMAGE_PNG_VALUE, "test1".getBytes()),
                new MockMultipartFile("test2", "test2.PNG", MediaType.IMAGE_PNG_VALUE, "test2".getBytes())
        );
        PostCreateRequest req = createPostCreateRequestWithImages(imageFiles);

        // when, then
        mockMvc.perform(
                multipart("/api/posts")
                        .file("images", imageFiles.get(0).getBytes()) // 2
                        .file("images", imageFiles.get(1).getBytes())
                        .param("title", req.getTitle())
                        .param("content", req.getContent())
                        .param("price", String.valueOf(req.getPrice()))
                        .param("memberId", String.valueOf(req.getMemberId()))
                        .param("categoryId", String.valueOf(req.getCategoryId()))
                        .with(requestPostProcessor -> { // 3
                            requestPostProcessor.setMethod("POST");
                            return requestPostProcessor;
                        })
                        .contentType(MediaType.MULTIPART_FORM_DATA)) // 4
                .andExpect(status().isCreated());

        verify(postService).create(postCreateRequestArgumentCaptor.capture()); // 5

        PostCreateRequest capturedRequest = postCreateRequestArgumentCaptor.getValue(); // 6
        assertThat(capturedRequest.getImages().size()).isEqualTo(2);
    }
}

패키지 명을 확인할 수 있도록 import문은 생략하지 않겠습니다.

 

1. 요청을 통해 컨트롤러에서 @ModelAttribute로 전달받을 PostCreateRequest를 캡쳐할 수 있도록 선언해둡니다.

2. multipart()를 이용하여 mutlipart/form-data 요청을 보내기 위한 데이터들을 지정해줍니다.

3. 해당 요청은 POST 메소드임을 지정해줍니다.

4. content type을 지정해줍니다.

5. Mock으로 만들어둔 PostService.create가 호출되는지 확인하고, 전달되는 인자를 캡쳐하였습니다.

6. 캡쳐된 값을 꺼내고, 정상적으로 두 건의 이미지가 업로드된 것인지 검증하였습니다.

 

 

포스트맨을 이용하여 요청을 테스트해봅시다.

포스트맨으로 테스트

로그를 남겨보면 요청된 데이터를 확인할 수 있을 것입니다.

 

 

이를 구현하기 위한 자세한 내용은,

2021.12.10 - [Spring/게시판 만들기] - 스프링부트 게시판 API 서버 만들기 (20) - 게시글 - 엔티티 설계

2021.12.12 - [Spring/게시판 만들기] - 스프링부트 게시판 API 서버 만들기 (21) - 게시글 - 생성

위 링크에 정리되어 있습니다.

 

반응형

+ Recent posts