๊ด€๋ฆฌ ๋ฉ”๋‰ด

th42500์˜ TIL

[Test] MockTest ์ค‘ ๋ฐœ์ƒํ•œ 401 (Unauthorized) ์—๋Ÿฌ ๋ณธ๋ฌธ

Backend/JUnit & BDD

[Test] MockTest ์ค‘ ๋ฐœ์ƒํ•œ 401 (Unauthorized) ์—๋Ÿฌ

th42500 2023. 1. 1. 14:02

๐Ÿ›  ๊ฐœ๋ฐœํ™˜๊ฒฝ

Editor : Intellij Ultimate

Deb Tool : SpringBoot 2.7.5

JDK : JAVA 11

Build : Gradle 6.8

Server : AWS EC2

DB : MySql 8.0

Library : SpringBoot Web, MySQL, Spring Data JPA, Lombok, Spring Security

 

 

โ“ ๊ธฐ๋Šฅ Test ์ง„ํ–‰ ์ค‘ ๋ฐœ์ƒํ•œ 401 Error โ“

์ตœ๊ทผ Mock์„ ์ด์šฉํ•œ BDD ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์—ฐ์Šต์„ ํ•˜๊ณ  ์žˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•œ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ ์ค‘ 401Error๋ฅผ ๋งˆ์ฃผ์ณค๋‹ค.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.likelion.healing.domain.dto.CommentReq;
import com.likelion.healing.domain.entity.CommentEntity;
import com.likelion.healing.domain.entity.PostEntity;
import com.likelion.healing.domain.entity.UserEntity;
import com.likelion.healing.service.CommentService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
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.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(CommentController.class)
class CommentControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    CommentService commentService;

    @Autowired
    ObjectMapper objectMapper;

    @Nested
    @DisplayName("๋Œ“๊ธ€ ๋“ฑ๋ก ํ…Œ์ŠคํŠธ")
    class AddCommentTest{

        @Test
        @DisplayName("๋Œ“๊ธ€ ๋“ฑ๋ก ์„ฑ๊ณต")
        void successfulAddComment() throws Exception {
            CommentReq req = CommentReq.builder()
                    .comment("comment")
                    .build();

            CommentEntity comment = CommentEntity.builder()
                                            .id(1)
                                            .comment("comment")
                                            .post(PostEntity.builder().id(3).build())
                                            .user(UserEntity.builder().userName("user").build())
                                            .build();
            LocalDateTime nowTime = LocalDateTime.now();
            comment.setCreatedAt(nowTime);
            comment.setUpdatedAt(nowTime);

            given(commentService.addCommentByPostId(any(Integer.class), any(CommentReq.class), any(String.class)))
                    .willReturn(comment);

            mockMvc.perform(post(String.format("/api/v1/posts/%d/comments", 3))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.id").value(1))
                    .andExpect(jsonPath("$.result.comment").value("comment"))
                    .andExpect(jsonPath("$.result.postId").value(3))
                    .andExpect(jsonPath("$.result.userName").value("user"))
                    .andExpect(jsonPath("$.result.createdAt").exists())
                    .andExpect(jsonPath("$.result.lastModifiedAt").exists());
        }
    }

}

 

 

๐Ÿ‘€ 401(Unauthorized) ๊ฐ€ ๋ฌด์Šจ ์—๋Ÿฌ์ผ๊นŒ

401์—๋Ÿฌ๋Š” Unauthorized๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์š”์ฒญํ•œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฆ๋ช…๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ์ด ๊ฑฐ๋ถ€๋˜์—ˆ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ์ฆ‰, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ์ฆ๋˜์ง€ ์•Š์•„ ์š”์ฒญ์„ ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์Œ์„ ์˜๋ฏธํ•˜๋Š” ์ƒํƒœ์ฝ”๋“œ์ด๋‹ค.

 

ํ•ด๋‹น ์—๋Ÿฌ๋Š” 403์—๋Ÿฌ์ธ Forbidden๊ณผ ์œ ์‚ฌํ•ด๋ณด์ด์ง€๋งŒ ์ „ํ˜€ ๋‹ค๋ฅด๋‹ค. 403 ์—๋Ÿฌ๋Š” ์ฆ๋ช…๋œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญ์„ ํ•˜์˜€์ง€๋งŒ ํ•ด๋‹น ๋ฆฌ์†Œ์Šค๋ฅผ ์ด์šฉํ•  ๊ถŒํ•œ์ด ์—†์–ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์‰ฝ๊ฒŒ ์˜ˆ์‹œ๋ฅผ ๋“ค์ž๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

401 - ๋กœ๊ทธ์ธ์ด ์•ˆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๊ธ€์— ๊ธ€์“ฐ๊ธฐ๋ฅผ ์š”์ฒญ

403 - ํšŒ์›์œผ๋กœ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ๋ฅผ ์š”์ฒญ

 

 

๐Ÿ’ก ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

Test Class ์ƒ๋‹จ์— @WithMockUser๋ฅผ ์„ ์–ธํ•จ์œผ๋กœ์จ ์ธ์ฆ๋œ ์ƒํƒœ์˜ User๋ฅผ ๊ฐ€์ •ํ•œ ์ƒํƒœ์—์„œ Test๋ฅผ ์ง„ํ–‰ํ•จ์œผ๋กœ์จ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

package com.likelion.healing.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.likelion.healing.domain.dto.CommentReq;
import com.likelion.healing.domain.entity.CommentEntity;
import com.likelion.healing.domain.entity.PostEntity;
import com.likelion.healing.domain.entity.UserEntity;
import com.likelion.healing.service.CommentService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
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.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.time.LocalDateTime;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(CommentController.class)
@WithMockUser(username = "user")  // โœจ
class CommentControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    CommentService commentService;

    @Autowired
    ObjectMapper objectMapper;

    @Nested
    @DisplayName("๋Œ“๊ธ€ ๋“ฑ๋ก ํ…Œ์ŠคํŠธ")
    class AddCommentTest{

        @Test
        @DisplayName("๋Œ“๊ธ€ ๋“ฑ๋ก ์„ฑ๊ณต")
        void successfulAddComment() throws Exception {
            CommentReq req = CommentReq.builder()
                    .comment("comment")
                    .build();

            CommentEntity comment = CommentEntity.builder()
                                            .id(1)
                                            .comment("comment")
                                            .post(PostEntity.builder().id(3).build())
                                            .user(UserEntity.builder().userName("user").build())
                                            .build();
            LocalDateTime nowTime = LocalDateTime.now();
            comment.setCreatedAt(nowTime);
            comment.setUpdatedAt(nowTime);

            given(commentService.addCommentByPostId(any(Integer.class), any(CommentReq.class), any(String.class)))
                    .willReturn(comment);

            mockMvc.perform(post(String.format("/api/v1/posts/%d/comments", 3))
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsBytes(req)))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.resultCode").value("SUCCESS"))
                    .andExpect(jsonPath("$.result.id").value(1))
                    .andExpect(jsonPath("$.result.comment").value("comment"))
                    .andExpect(jsonPath("$.result.postId").value(3))
                    .andExpect(jsonPath("$.result.userName").value("user"))
                    .andExpect(jsonPath("$.result.createdAt").exists())
                    .andExpect(jsonPath("$.result.lastModifiedAt").exists());
        }
    }
}

์ •์˜๋œ MockUser๋Š” "user"๋ผ๋Š” userName๊ณผ "password"๋ผ๋Š” password ๊ทธ๋ฆฌ๊ณ  "ROLE_USER"๋ผ๋Š” ๊ถŒํ•œ์„ ๊ฐ–๊ณ  ์žˆ์Œ์„ ๊ฐ€์ •ํ•ด์ฃผ์–ด ๋”์ด์ƒ 401(Unathorized)๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 

์ฐธ๊ณ  ๋ฌธ์„œ

https://developer.mozilla.org/ko/docs/Web/HTTP/Status/401

 

401 Unauthorized - HTTP | MDN

์ด ์ƒํƒœ๋Š” WWW-Authenticate (en-US) ํ—ค๋”์™€ ํ•จ๊ป˜ ์ „์†ก๋˜๋ฉฐ, ์ด ํ—ค๋”๋Š” ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ธ์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

developer.mozilla.org

https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html/test.html#test-method-withmockuser

 

18. Testing

There are a number of options available to associate a user to the current HttpServletRequest. For example, the following will run as a user (which does not need to exist) with the username "user", the password "password", and the role "ROLE_USER": You can

docs.spring.io

https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0

 

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ฃผ์š” ์•„ํ‚คํ…์ฒ˜ ์ดํ•ด

๋ชฉ์ฐจ

catsbi.oopy.io

 

'Backend > JUnit & BDD' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

MockTest ์‹œ null ๊ฐ’์ด ๋œจ๋Š” ์ด์œ   (0) 2023.01.26
[JUnit] @Nested๋ž€?  (0) 2023.01.04
Given-When-Then ํŒจํ„ด๊ณผ given()&when()  (0) 2023.01.03
Comments