๐Ÿช kakaotech campus

์นดํ…Œ์ผ | [STEP2 cloncoding] 4์ฃผ์ฐจ ๊ณผ์ œ - (2)

c0zi 2023. 7. 20. 20:34

4์ฃผ์ฐจ

์นด์นด์˜ค ํ…Œํฌ ์บ ํผ์Šค 2๋‹จ๊ณ„ - BE - 4์ฃผ์ฐจ ํด๋ก  ๊ณผ์ œ

 

๊ณผ์ œ๋ช…

1. ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ

 

๊ณผ์ œ ์„ค๋ช…

1. ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋’ค ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์—…๋กœ๋“œํ•˜์‹œ์˜ค.

2. stub์„ ๊ตฌํ˜„ํ•˜์‹œ์˜ค.

 

๊ณผ์ œ ์ƒ์„ธ : ๊ณผ์ œ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ, ์œ ๋…ํ•ด์•ผํ•  ๊ฒƒ

์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ํ…Œ์ŠคํŠธ๊ฐ€ ๊ตฌํ˜„๋˜์—ˆ๋Š”๊ฐ€?
Mockito๋ฅผ ์ด์šฉํ•˜์—ฌ stub์„ ๊ตฌํ˜„ํ•˜์˜€๋Š”๊ฐ€?
์ธ์ฆ์ด ํ•„์š”ํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?
200 ok๋งŒ ์ฒดํฌํ•œ ๊ฒƒ์€ ์•„๋‹Œ๊ฐ€? (ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ œ์ผ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ตฌํ˜„๋˜์—ˆ๋Š”๊ฐ€?)
๋ชจ๋“  ์š”์ฒญ๊ณผ ์‘๋‹ต์ด json์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ๋Š”๊ฐ€?

 


1. ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ


01) ์ปจํŠธ๋กค๋Ÿฌ ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋’ค ์†Œ์Šค์ฝ”๋“œ๋ฅผ ์—…๋กœ๋“œํ•˜์‹œ์˜ค.

 


๐Ÿ” ๊ธฐ์กด ์ฝ”๋“œ ๋ถ„์„

 

GlobalExceptionHandler

 

@RequiredArgsConstructor
@Component
public class GlobalExceptionHandler {

    private final ErrorLogJPARepository errorLogJPARepository;

    public ResponseEntity<?> handle(RuntimeException e, HttpServletRequest request){
        if(e instanceof Exception400){
            Exception400 ex = (Exception400) e;
            return new ResponseEntity<>(
                    ex.body(),
                    ex.status()
            );
        }else if(e instanceof Exception401){
            Exception401 ex = (Exception401) e;
            return new ResponseEntity<>(
                    ex.body(),
                    ex.status()
            );
        }else if(e instanceof Exception403){
            Exception403 ex = (Exception403) e;
            return new ResponseEntity<>(
                    ex.body(),
                    ex.status()
            );
        }else if(e instanceof Exception404){
            Exception404 ex = (Exception404) e;
            return new ResponseEntity<>(
                    ex.body(),
                    ex.status()
            );
        }else if(e instanceof Exception500){
            ErrorLog errorLog = ErrorLog.builder()
                    .message(e.getMessage())
                    .userAgent(request.getHeader("User-Agent"))
                    .userIp(request.getRemoteAddr())
                    .build();
            errorLogJPARepository.save(errorLog);
            Exception500 ex = (Exception500) e;
            return new ResponseEntity<>(
                    ex.body(),
                    ex.status()
            );
        }else{
            ErrorLog errorLog = ErrorLog.builder()
                    .message(e.getMessage())
                    .userAgent(request.getHeader("User-Agent"))
                    .userIp(request.getRemoteAddr())
                    .build();
            errorLogJPARepository.save(errorLog);
            return new ResponseEntity<>(
                    "unknown server error",
                    HttpStatus.INTERNAL_SERVER_ERROR
            );
        }
    }
}

 

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” GlobalExceptionHandler ํด๋ž˜์Šค์ด๋‹ค. ํŠน์ • ์˜ˆ์™ธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ์‘๋‹ต์„ ์ƒ์„ฑํ•œ๋‹ค. ๊ฐ ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

handle ๋ฉ”์„œ๋“œ๊ฐ€ RuntimeException๊ณผ HttpServletRequest๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๋Š”๋‹ค. RuntimeException์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ResponseEntity๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

 

  • Exception400~404์˜ ๊ฒฝ์šฐ, ์˜ˆ์™ธ ๊ฐ์ฒด์ธ ex์—์„œ ์‘๋‹ต ๋ฐ์ดํ„ฐ์™€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€ ResponseEntity๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • Exceotion500์ด ๊ฒฝ์šฐ์—๋Š”, ์˜ˆ์™ธ ๊ฐ์ฒด์ธ ex์—์„œ ์‘๋‹ต ๋ฐ์ดํ„ฐ์™€ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์˜จ ๋’ค, ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•œ ๋’ค, ResponseEntity๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • ๊ทธ ์™ธ์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, "unknown server error" ๋ฉ”์‹œ์ง€์™€ HttpStatus.INTERNAL_SERVER_ERROR ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋™์‹œ์— ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•œ๋‹ค.

 

UserRestControllerTest

 

// GlobalExceptionHandler์™€ UserRestController๋ฅผ SpringContext์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

@Import({
        SecurityConfig.class,
        GlobalExceptionHandler.class
})
@WebMvcTest(controllers = {UserRestController.class})
public class UserRestControllerTest {

    // ๊ฐ์ฒด์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๋Š” ์ถ”์ƒ๋ฉ”์„œ๋“œ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค. (๊ฐ€์งœ๋กœ ๋งŒ๋“ค๋ฉด)
    // ํ•ด๋‹น ๊ฐ์ฒด๋Š” SpringContext์— ๋“ฑ๋ก๋ฉ๋‹ˆ๋‹ค.
    @MockBean
    private UserService userService;

    @MockBean
    private ErrorLogJPARepository errorLogJPARepository;

    // @WebMvcTest๋ฅผ ํ•˜๋ฉด MockMvc๊ฐ€ SpringContext์— ๋“ฑ๋ก๋˜๊ธฐ ๋•Œ๋ฌธ์— DIํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    @Autowired
    private MockMvc mvc;

    // @WebMvcTest๋ฅผ ํ•˜๋ฉด ObjectMapper๊ฐ€ SpringContext์— ๋“ฑ๋ก๋˜๊ธฐ ๋•Œ๋ฌธ์— DIํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    @Autowired
    private ObjectMapper om;

    @Test
    public void t1(){}

    @Test
    public void join_test() throws Exception {
        // given
        UserRequest.JoinDTO requestDTO = new UserRequest.JoinDTO();
        requestDTO.setEmail("ssarmango@nate.com");
        requestDTO.setPassword("meta1234!");
        requestDTO.setUsername("ssarmango");
        String requestBody = om.writeValueAsString(requestDTO);

        // when
        ResultActions result = mvc.perform(
                MockMvcRequestBuilders
                        .post("/join")
                        .content(requestBody)
                        .contentType(MediaType.APPLICATION_JSON)
        );

        String responseBody = result.andReturn().getResponse().getContentAsString();
        System.out.println("ํ…Œ์ŠคํŠธ : "+responseBody);

        // then
        result.andExpect(MockMvcResultMatchers.jsonPath("$.success").value("true"));
    }

    @Test
    public void login_test() throws Exception {
        // given
        UserRequest.LoginDTO loginDTO = new UserRequest.LoginDTO();
        loginDTO.setEmail("ssar@nate.com");
        loginDTO.setPassword("meta1234!");
        User user = User.builder().id(1).roles("ROLE_USER").build();
        String requestBody = om.writeValueAsString(loginDTO);

        // stub -> ๊ฐ€์ •๋ฒ•
        String jwt = JWTProvider.create(user);
        Mockito.when(userService.login(any())).thenReturn(jwt); // token

        // when
        ResultActions result = mvc.perform(
                MockMvcRequestBuilders
                        .post("/login")
                        .content(requestBody)
                        .contentType(MediaType.APPLICATION_JSON)
        );
        String responseBody = result.andReturn().getResponse().getContentAsString();
        String responseHeader = result.andReturn().getResponse().getHeader(JWTProvider.HEADER);
        System.out.println("ํ…Œ์ŠคํŠธ : "+responseBody);
        System.out.println("ํ…Œ์ŠคํŠธ : "+responseHeader);

        // then
        result.andExpect(MockMvcResultMatchers.jsonPath("$.success").value("true"));
        Assertions.assertTrue(jwt.startsWith(JWTProvider.TOKEN_PREFIX));
    }

    @Test
    public void length_test(){
        String value = "Bearer eyJ0eX";
        System.out.println(value.substring(0,6));
    }

}

 

User์— ๋Œ€ํ•œ ๊ธฐ๋Šฅ๋“ค์„ controller ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํด๋ž˜์Šค์ด๋‹ค. ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ๋Š” @WebMvcTest๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UserRestController ํด๋ž˜์Šค์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. `UserRestController`์™€ `GlobalExceptionHandler`๋ฅผ Spring ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋กํ•˜๊ณ , ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ…Œ์ŠคํŠธํ•œ๋‹ค.


join_test()


ํšŒ์›๊ฐ€์ž…์— ํ•„์š”ํ•œ ํ…Œ์ŠคํŠธ์ด๋‹ค. UserRequest.JoinDTO ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ JSON ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ POST ์š”์ฒญ์œผ๋กœ ์ „์†กํ•œ๋‹ค. ๋˜ํ•œ, ์‘๋‹ต์œผ๋กœ ์˜ค๋Š” JSON ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.


login_test()


๋กœ๊ทธ์ธ ์š”์ฒญ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋กœ, UserRequest.LoginDTO ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ JSON ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ POST ์š”์ฒญ์œผ๋กœ ์ „์†กํ•œ๋‹ค. ๋กœ๊ทธ์ธ ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” JWT ํ† ํฐ์„ ๊ฒ€์ฆํ•˜๊ณ , ํ† ํฐ์˜ ํ˜•์‹์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.


length_test()

 

๋ฌธ์ž์—ด ๊ธธ์ด์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋กœ, ์ฃผ์–ด์ง„ ๋ฌธ์ž์—ด์—์„œ ์ผ๋ถ€๋ฅผ ์ž˜๋ผ๋‚ด์–ด ๊ฒ€์ฆํ•œ๋‹ค.

Spring ์ปจํ…์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋™์ž‘์„ ๊ฒ€์ฆํ•˜๊ณ , ๊ฐ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ์ƒํƒœ ๋ฐ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค. ํŠนํžˆ MockBean์„ ์‚ฌ์šฉํ•˜์—ฌ UserService์™€ ErrorLogJPARepository๋ฅผ ๊ฐ€์งœ ๊ฐ์ฒด๋กœ ๋Œ€์ฒดํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š”๋ฐ, ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ , ์˜ˆ์ƒ๋œ ๊ฒฐ๊ณผ๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.