[Spring] 회원가입, 로그인 (익셉션 핸들러 / 유닛테스트) - 블로그제작 (1)

merci·2023년 2월 4일
1

블로그 제작 V1

목록 보기
1/9
post-thumbnail
post-custom-banner

이번에는 간단한 블로그를 만들어 보자

블로그 만들기

블로그를 만들기 앞서 간략하게 배워볼 내용을 정리 해보자

  1. BootStrap ( 트위터 ) 사용

  2. Exception 처리 방법

  3. 권한이 있는 유저 테이블 / 게시글, 댓글, 좋아요 테이블 사용

  4. 책임에 맞게 분리

컨트롤러의 책임

  • 유효성 검사
  • 인증 / 권한
  • 응답 ( 뷰, 데이터 )

서비스의 책임

  • 트랜잭션 처리
  • 비즈니스 로직 처리
  • 권한 처리 ( DB 접근 )

레파지토리의 책임

  • DML ( 조작 )
  • DQL ( 조회 )

레파지토리와 DB는 드라이버를 통해 데이터가 통신한다.
자바가 사용하는 jdbc 드라이버는 ResultSet 으로 데이터를 리턴한다.
ResultSet의 데이터를 클래스에 넣으면 자바에서 편하게 관리할 수가 있는데 이때 사용하는 클래스를 엔티티라고 한다.
Mybatis 라이브러리를 사용하여 이러한 과정을 간편하게 하자.

번외로, MongoDB는 json 을 가지고 있어서 Gson라이브러리로 바로 받을 수 있다.


이후 좀더 심화 과정으로 아래의 두 단계까지 해보자

  1. OAuth ( 사진업로드 )

  2. Redis ( 쉽게 말해 메모리로 가져온 데이터 )




협업 프로젝트의 과정

  1. 프로젝트 기획

  2. 화면 설계

  3. 화면 그리기 (공통적인 것 분리, 임무 분담, 일정 계획)

  4. 모델링 ( 애자일 )
    1 - 핵심로직, 큰틀부터 제작 ( 테이블 )
    ( 블로그의 핵심은 글쓰기, 수정, 삭제, 댓글 )
    2 - 핵심로직이 아닌것들 정리, 제작
    3 - 세부사항 처리

    1, 2, 3 단계를 문서화 시킨다 / 중요 내용 빠뜨리지 않게 구체적으로 작성

  5. 기능정리 ( 중요도 순 )
    1 - 회원가입, 로그인, 아이디 중복체크, 글 목록보기, 상세보기, 수정, 삭제
    2 - 댓글 쓰기, 댓글 목록보기, 댓글 수정하기, 회원 수정하기
    3 - 검색, 페이징, 좋아요
    4 - 댓글 좋아요 등

  6. 임무분담 + 프로젝트 시작
    같은 범주를 묶어서 임무를 분담 ( 팀장 지시 )
    일정관리는 트렐로같은 프로그램으로 간편하게 한다





1. 화면 설계

  • 필요한 화면 정리 ( 메인, 회원가입, 로그인, 상세보기, 글 수정, 회원 수정... )

  • 간단한 레이아웃 설계, 사이트맵 연결등을 간단하게 만든다

oven 으로 간단하게 그렸다

메인 화면

로그인 화면

상세보기 화면

회원가입, 수정 화면

2. 화면 그리기

부트스트랩을 이용하자

summernote 를 가져왔다


썸머노트에 필요한 cdn

	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script src="https://kit.fontawesome.com/32aa2b8683.js" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" />
    <link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-lite.min.js"></script>

화면은 이렇게만 부트스트랩으로 만들고 스프링으로 서버 로직을 만들어 보자

3. 모델링

모델링 작업의 순서

  • 라이브러리 확인

  • xml,yml 세팅 확인

  • 간단하게 jsp 연결해서 서버 동작하는지 확인

  • 로직 작성

뷰 가져오기

부트스트랩으로 만들어 놓은 간단한 뷰들을 스프링 프로젝트에 복사한다
간단한 컨트롤러를 생성해서 주소연결이 제대로 나오는지 확인한다




README.md 작성



3-1 모델링 + 더미 데이터 생성

  • table.sql
create table user_tb (
    id int auto_increment primary key,
    username varchar not null unique,
    password varchar not null,
    email varchar not null,
    created_at timestamp not null
);

create table board_tb (
    id int auto_increment primary key,
    title varchar not null,
    content longtext not null,  // 이미지, 영상을 게시할 수도 있으니 varchar는 부족함
    user_id int not null,
    created_at timestamp not null
);

create table reply_tb (
    id int auto_increment primary key,
    content varchar,
    board_id int not null,
    user_id int not null,
    created_at timestamp not null
);

create table love_tb (
    id int auto_increment primary key,
    count int    
);
  • data.sql ( 임시 더미 데이터 )
insert into user_tb ( username, password, email, created_at) values ('ssar','1234','ssar@nate.com',now());
insert into user_tb ( username, password, email, created_at) values ('love','1234','love@nate.com',now());

insert into board_tb ( title,content, user_id, created_at ) values ('안녕하세요 !!','오늘날씨가 좋네요 ㅎㅎㅎ',1,now());
insert into board_tb ( title,content, user_id, created_at ) values ('두번째 글입니다.','두번째 내용',2,now());
insert into board_tb ( title,content, user_id, created_at ) values ('세번째 글입니다.','세번째 내용',1,now());
insert into board_tb ( title,content, user_id, created_at ) values ('네번째 글입니다.','네번째 내용',1,now());
insert into board_tb ( title,content, user_id, created_at ) values ('다섯번째 글입니다.','다섯번째 내용',2,now());
insert into board_tb ( title,content, user_id, created_at ) values ('여섯번째 글입니다.','여섯번째 내용',2,now());

insert into reply_tb (content, board_id, user_id, created_at ) values ('1등 ㅋㅋㅋ', 1, 1,now());
insert into reply_tb (content, board_id, user_id, created_at ) values ('파이팅', 2, 2,now());
insert into reply_tb (content, board_id, user_id, created_at ) values ('세번째 댓글', 3, 2,now());

insert into love_tb (count) values (1);
commit;

지금까지는 기본적으로 사용할 것 같은 쿼리를 작성하고 모델을 만들었는데 약간 다른 순서로 해볼 생각이다.

기본 모델 생성


3-2 컨트롤러 생성

유저 컨트롤러 작성

만들어 놓은 뷰 - 회원가입 폼을 보면 어떤 기능이 필요할지 유추할수 있다

이런 회원가입창이 있을때 회원가입 버튼을 누르면 폼태그의 데이터들이 x-www-form~ 형식으로 매핑된 컨트롤러로 전송이 된다.
만약 회원가입에 입력할 내용들이 10개가 넘어가게 될 경우 ( 주소 포함 )
컨트롤러에서 파라미터로 다 받는다면 컨트롤러가 너무 길어지고 가독성이 불편해진다.

따라서 회원가입폼의 데이터들을 받을 클래스를 하나 만들어서 컨트롤러가 파라미터로 가지면 가독성도 좋아지고 데이터 관리하기가 더 좋아진다.

회원가입 데이터를 받을 dto를 만들자


UserReqstatic 멤버 클래스로 작성해서 요청과 관련된 dto는 여기에 다 모을 예정이다.
정적 멤버 클래스로 생성하면 외부클래스의 객체를 만들지 않고 UserReq.JoinReqDto 같은 모양으로 바로 내부클래스의 객체를 만들 수가 있다.
static import를 하면 JoinReqDto만 단독으로 사용할수 있다.

public class UserReq {

    @Setter
    @Getter
    public static class JoinReqDto {
        private String username;
        private String password;
        private String email;
    }
}

이제 컨트롤러가 JoinReqDto 를 파라미터로 받는다

	@PostMapping("/join")
    public String join(JoinReqDto joinReqDto) {
		// 작성 예정
        return "redirect:/loginForm";
    }

이제 컨트롤러에서 유효성 검사를 하고 유저서비스를 호출해서 회원가입 서비스를 만들면 된다.
컨트롤러의 파라미터가 제대로 들어오지 않으면 ( 폼태그에 required 를 추가하겠지만 포스트맨으로 공격할 수도 있기 때문 ) 들어오지 않은 파라미터마다 클라이언트에게 알려야하는데 이러한 귀찮은 과정을 간편하게 해주는 익셉션처리에 대해서 알아보자.


익셉션 핸들러

파라미터로 들어온 데이터가 유효성이 없을때 임의적으로 런타임 익셉션을 발생시켜서 관리해보자.

한마디로, 내가 발생시킬 익센셥 전용 컨트롤러다


핸들러 폴더를 만들고 RuntimeException을 상속하는 CustomException 클래스를 만든다

public class CustomException extends RuntimeException {
    public CustomException(String msg) {
        super(msg);
    }
}

다음은 생성한 CustomException 의 메세지를 클라이언트에게 알리는 CustomExceptionHanlder 를 만든다.

import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog.handler.ex.CustomException;
import shop.mtcoding.blog.util.Script;

@RestControllerAdvice
public class CustomExceptionHanlder {

    @ExceptionHandler(CustomException.class)   
    public String customException(CustomException e) {
        return Script.back(e.getMessage());   // 익셉션 메세지
        					// RestController 가 메세지를 리턴
    }
}

@RestControllerAdvice@ExceptionHandler 를 이용하면 CustomException 이 발생했을때 핸들러가 에러 메세지를 가져와서 customException() 메소드로 연결시킨다.

메세지를 받은 Script.back의 코드는

public class Script {
    public static String back(String msg) {
        StringBuffer sb = new StringBuffer();
        sb.append("<script>");
        sb.append("alert('" + msg + "');");
        sb.append("history.back();");
        sb.append("</script>");
        return sb.toString();
    }
}

컨트롤러에서 익셉션을 생성했을 경우

	throw new CustomException("username을 작성해주세요");

컨트롤러에서 익셉션을 발생시키면 익셉션이 스크립트로된 문자열을 리턴하고 메세지컨버터가 이 문자열을 응답데이터에 리턴한다.
이후 브라우저의 자바스크립트 엔진이 스크립트를 실행한다.

익셉션 핸들러를 컨트롤러에서 이용하자

화원가입시 익셉션 핸들러를 사용한다면

    @PostMapping("/join")
    public String join(JoinReqDto joinReqDto){    
        if (joinReqDto.getUsername() == null || joinReqDto.getUsername().isEmpty()) {
            throw new CustomException("username을 작성해주세요");
        }
        if (joinReqDto.getPassword() == null || joinReqDto.getPassword().isEmpty()) {
            throw new CustomException("password를 작성해주세요");
        }
        if (joinReqDto.getEmail() == null || joinReqDto.getEmail().isEmpty()) {
            throw new CustomException("email을 작성해주세요");
        }
        userService.회원가입(joinReqDto);      
        return "redirect:/loginForm";      
    }

파라미터로 넘어온 데이터가 없으면 new CustomException 으로 익센션을 발생시켜 에러메세지를 넘긴다.
회원가입에 필요한 모든 데이터가 정상적으로 입력이 된다면 익센셥이 발생하지 않고 회원가입 서비스를 실행하고 redirect:/loginForm 으로 이동하게 된다.

이렇게 회원가입 시의 익셉션을 모두 잡고 서비스 로직을 만들면 이후 발생한 익셉션은 컨트롤러에서 발생하지 않았다는게 보장이 된다. -> 디버깅할때 좋다

이제 작성한 익셉션 핸들러가 제대로 동작하는지 확인을 해야하는데 회원가입폼을 열어서 매번 직접 입력을 하면 너무 비효율적이다.
포스트맨을 통해서 데이터를 입력할수도 있지만 이 방법도 번거롭기는 마찬가지다.

스프링은 이때 간편하게 필요한 테스트를 할 수 있도록 유닛테스트를 제공한다.


유닛 테스트

하나의 메소드가 제대로 만들어졌는지 유닛테스트를 통해서 검증할 수가 있는데 스프링부트에서는 여러 어노테이션을 이용하여 유닛테스트를 간편하게 할 수 있도록 도와준다.

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class UserControllerTest {
    @Autowired
    private MockMvc mvc; // @AutoConfigureMockMvc 가 DI 를 해줌

    @Test
    public void join_test() throws Exception {
        // given
        String requestBody = "username=cos&password=1234&email=cos@nate.com"; 
        // when
        ResultActions resultActions = mvc.perform(post("/join").content(requestBody)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
                						// = application/x-www-form-urlencoded
        // then
        resultActions.andExpect(status().is3xxRedirection());
    }
}
  • @SpringBootTest통합테스트 환경을 만들어준다. ( 실제 환경과 동일하게 인스턴스가 메모리에 생성됨 -> 독립적으로 테스트 가능한 환경이 된다

  • 여기엔 없지만 @WebMvcTest("컨트롤러".class) 는 필터, DS, 컨트롤러만 만들어서 컨트롤러를 테스트할 수 있는 환경을 만들어준다.

  • webEnvironment 속성은 테스트의 웹 환경을 설정하는데 디폴트값은 WebEnvironment.MOCK 이다 -> Mock ( 가짜 환경에 IoC 컨테이너 테스트 하겠다. )

  • WebEnvironment.MOCK 으로 실제 서블릿 컨테이너를 띄우지 않고 Mocking한 컨테이너가 실행된다. 보통 MockMvc와 같이 사용된다.

  • @AutoConfigureMockMvc 가 Mock환경의 IoC컨테이너에 MockMvc Bean 을 생성해준다.

  • 생성된 MockMvc@Autowird로 의존성을 주입( DI ) 하면 테스트 환경이 완성된다.

  • requestBody 에 요청데이터를 key=value 형태로 작성하는데 스프링의 기본파싱전략이 x-www~ 로 key=value 의 형태의 데이터가 "/join"의 매핑메소드로 들어간다

  • content에 데이터를 넣고 contentType으로 타입을 넣어주면 응답의 결과를 가지고 있는 ResultActionsandExpect 메소드로 예상되는 상태코드를 테스트 할 수 있다.

  • MockMvc 가 실제 존재하는 메소드를 Mock환경에 만들어 requestBody 의 데이터를 넣어 테스트를 하는데 content의 인수로 데이터를 넣고, contentType의 파라미터에 데이터의 타입을 넣는다

  • 실제 메소드의 리턴도 "redirect:/"이므로 MockMvc의 리턴타입인 ResultActions 의 예상데이터를 andExpect 로 작성하는데 실제로 302 리다이렉션을 하므로 테스트결과가 status().is3xxRedirection()로 나온다면 테스트가 통과된다.

이러한 유닛테스트를 통해서 테스트가 필요할때 확실히 검증하고 넘어가면 이후 발생하는 에러는 테스트가 끝난 기능에서 발생하지 않았다는게 보장이 된다.

이제 중간중간 만들어지는 익셉션 핸들러를 유닛테스트를 통해 검증하면서 코드를 작성해보자

이제 기능을 추가해보자

회원가입 중복체크 기능추가

중복체크는 이전 포스팅을 참고하여 만들었다.

<button type="button" id="usernameCheck">중복확인</button>
 <script>
   let joinOk = false;

        function valid(){
            if ( joinOk ){
                if( $('#password').val()===$('#passwordCheck').val()) {
                    return true;
   					// 중복체크 & 패스워드 동일하면 true
                }else{
                    alert('패스워드가 다릅니다')
                    return false;
                }
            }else{
                alert('아이디 중복확인이 필요합니다'); 
   							// 회원가입 버튼누르면 뜬다
                return false;
            }
        }
        $('#usernameCheck').click(()=>{ 
            let username = { username: $('#username').val() }
            
            $.ajax({
                type: "post",
                url: "/usernameCheck",
                data: JSON.stringify(username),
                headers:{
                    "content-type":"application/json; charset=utf-8"
                },
                dataType:"json"
            }).done((res) => { 
                if( res.code !== 1) {
                    alert(res.msg); // username을 입력해주세요
                }
                if( res.data === true){
                    alert(res.msg); // username 사용가능
                    joinOk = true;  // 중복체크 true
                }else{
                    alert(res.msg); // 동일한 username 존재
                    joinOk = false;
                }  
            }).fail((err) => {
                alert('실패');
            });
        });
  </script>

아이디 중복체크 응답에 json을 사용 했다.

@AllArgsConstructor
@Getter
@Setter
public class ResponseDto<T> {
    private int code;  // 1, -1 
    private String msg;  // 사유 
    private T data;  // 제네릭 사용으로 여러 오브젝트를 받을수 있다
}
@PostMapping("/usernameCheck")
public @ResponseBody ResponseDto<?> usernameCheck(
	   @RequestBody Map<String, Object> param){
    String username = param.get("username").toString();
    return userService.중복체크(username);
}

중복체크 서비스

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public ResponseDto<?> 중복체크(String username) {
        if (username == null || username.isEmpty()) {
            return new ResponseDto<>(-1, "username을 입력해주세요", null);
        }
        User sameUser = userRepository.findByUsername(username);
        if (sameUser != null) {
            return new ResponseDto<>(1, "동일한 username이 존재합니다", false);
        } else {
            return new ResponseDto<>(1, username + " 사용 가능", true);
        }
    }


회원가입 기능 추가

<form action="/join" method="post" onsubmit="return valid()">
  ....
<button type="submit" id="join-btn" class="btn btn-primary">회원가입</button>

회원가입 데이터를 받을 dto( JoinReqDto )

  public class UserReq {    
    @Getter
    @Setter
    public static class JoinReqDto{
        private String username;
        private String password;
        private String email;
    }
}

회원가입과 연결된 컨트롤러

    @PostMapping("/join")
    public String join(JoinReqDto joinReqDto){    
        if (joinReqDto.getUsername() == null || joinReqDto.getUsername().isEmpty()) {
            throw new CustomException("username을 작성해주세요");
        }
        if (joinReqDto.getPassword() == null || joinReqDto.getPassword().isEmpty()) {
            throw new CustomException("password를 작성해주세요");
        }
        if (joinReqDto.getEmail() == null || joinReqDto.getEmail().isEmpty()) {
            throw new CustomException("email을 작성해주세요");
        }
        userService.회원가입(joinReqDto);
        return "redirect:/loginForm";   
    }

서비스를 호출하기만 하면 서비스에서 모든 로직을 처리하도록 한다
서비스로 넘어가기 전에 컨트롤러의 모든 코드가 제대로 작동하는지 테스트를 하고 넘어가야 나중에 디버깅하기가 편해지므로 검증을 똑바로 하자

검증에는 위에서 소개한 유닛테스트를 이용해서 컨트롤러가 동작하는지 테스트한다

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class UserControllerTest {
    @Autowired
    private MockMvc mvc;
    @Test
    public void join_test() throws Exception {
        // given
        String requestBody = "username=monster&password=1234&email=monster@nate.com"; 
        // when
        ResultActions resultActions = mvc.perform(post("/join").content(requestBody)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
        // then
        resultActions.andExpect(status().is3xxRedirection());
    }
}

requestBody 의 데이터를 다양하게 변형해서 입력해보고 익셉션 핸들러가 제대로 작동하는지 확인한다.

중복확인하고 아이디를 바꿔 회원가입 할 수도 있어서 서비스에서 한번 더 중복확인을 한다

    @Transactional
    public void 회원가입(JoinReqDto joinReqDto) {
        User sameUser = userRepository.findByUsername(joinReqDto.getUsername());
        if (sameUser != null) {
            throw new CustomException("동일한 username이 존재합니다");
        }
        int result = userRepository.insertUser(
                joinReqDto.getUsername(), 
                joinReqDto.getPassword(), 
                joinReqDto.getEmail());
        if (result != 1) {
            throw new CustomException("회원가입실패");
        }        
    };

서비스에서 로직을 컨트롤러로 넘기지 않고( void ) 모두 처리해서 컨트롤러의 책임을 가볍게 만든다.

MyBatis에서 사용된 쿼리

<insert id="insertUser">
  insert into user_tb ( username, password, email, created_at ) 
  		values ( #{username}, #{password}, #{email}, now())
</insert>


ResponseEntity

지금까지 만든 익셉션 핸들러는 사용자의 잘못에 대한 에러 메세지를 보내는 역할을 한다.
이때 보내지는 상태코드는 200인데 사용자의 잘못에 대한 상태코드는 400이 되어야 한다.
따라서 익셉션 발생시 200이 아닌 코드를 보내야 하는데 이를 위해선 익셉션 핸들러에 약간의 변형을 줘야 한다.

200이 아닌 응답코드를 리턴하기 위해서는 ResponseEntity 라는 클래스를 이용하면 된다.
ResponseEntity 는 사용자의 요청, 응답의 HttpHeaderHttpBody를 가지는 HttpEntity 를 구현한 클래스로서 사용자의 요청에 따른 응답데이터를 돌려줄 수가 있다.
ResponseEntity 는 기본적으로 다양한 상태코드를 응답하고 바디데이터도 함께 보낼 수가 있다.

익셉션 핸들러

@Getter
public class CustomException extends RuntimeException{
    private HttpStatus status;

    public CustomException(String msg, HttpStatus status){
        super(msg);  // 메세지는 조상이 처리
        this.status = status; 
        // 경우에 따른 응답코드를 다르게 주고 싶어서 생성자의 파라미터로 추가했다.
    }

    public CustomException(String msg){
        this(msg, HttpStatus.BAD_REQUEST); // -> 400
        // 메세지만 입력하면 기본적으로 400을 응답하는 익셉션 핸들러
    }
}

사용자에게 응답할 상태코드인 HttpStatus 를 필드로 가지고 디폴트 상태코드가 400인 CustomException 를 오버로딩하여 만든다.

@RestControllerAdvice
public class CustomExceptionHandler {   

    @ExceptionHandler(CustomException.class)  
    public ResponseEntity<?> customException(CustomException e){
        return new ResponseEntity<>(Script.back(e.getMessage()), e.getStatus());      
    }
}

익셉션 핸들러는 ResponseEntity<?> 를 리턴하게 되고 에러메세지와 상태코드를 클라이언트에게 보낼수가 있게 되었다

회원가입 기능 에러 메세지

아이디 중복체크를 안했을 때

동일한 아이디가 존재할 때

중복되지 않는 아이디 입력

2개의 패스워드가 다를때

이메일 입력을 안했을 때

모두 정상적으로 입력하면

로그인 창으로 넘어간다


로그인 기능 추가

로그인도 회원가입과 유사하다

<form action="/login" method="post">
    <input type="text" name="username" placeholder="Enter username" required>
    <input type="password" name="password" placeholder="Enter password" required>
    <button type="submit" id="login-btn">로그인</button>
</form>

로그인 데이터를 받을 dto ( LoginReqDto )

public class UserReq {    
    @Getter
    @Setter
    public static class LoginReqDto{
        private String username;
        private String password;
    }
}

로그인을 받을 컨트롤러

    @PostMapping("/login")
    public String login(LoginReqDto loginReqDto){
        if (loginReqDto.getUsername() == null || loginReqDto.getUsername().isEmpty()) { 
            throw new CustomException("username을 작성해주세요");
        }
        if (loginReqDto.getPassword() == null || loginReqDto.getPassword().isEmpty()) {
            throw new CustomException("password를 작성해주세요");
        }
        User principal = userService.로그인(loginReqDto);
        session.setAttribute("principal", principal);
        return "redirect:/";
    }

좀 더 완벽한 로그인폼은 길이제한 + 한글 금지 + 정규표현식 등을 사용하지만 지금은 간단하게 구현만 한다

로그인의 익셉션 핸들러도 유닛테스트를 통해서 검증하고 넘어간다

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class UserControllerTest {
    @Autowired
    private MockMvc mvc;

    @Test
    public void login_test() throws Exception {
        // given
        String requestBody = "username=ssar&password=1234";

        // when
        ResultActions resultActions = mvc.perform(post("/login").content(requestBody)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)); 

        HttpSession session = resultActions.andReturn().getRequest().getSession();
        User principal = (User) session.getAttribute("principal");

        // then
        assertThat(principal.getUsername()).isEqualTo("ssar");
        resultActions.andExpect(status().is3xxRedirection());
    }
}

requestBody 를 변화를 줘서 테스트를 하고 로그인이 되면 세션에 유저 오브젝트가 저장된다.
andReturn() 를 통해서 다시 리턴할 수가 있다.
Assertions.assertThat api 로 테스트하면 로그인 테스트는 완료된다.

사용된 쿼리는

<select id="findByUsernameAndPassword" resultType="shop.mtcoding.blog.model.User">
      select * from user_tb where username = #{username} and password = #{password}
</select>

비밀번호 입력을 안하면

잘못 입력 했을 경우

아이디 패스워드 맞게 입력하면



지금까지는 기본적인 작업을 만들때마다 더미 유저로 로그인을 했었다.
매번 테스트를 할 때마다 로그인을 하려고 하면 비효율적이다.

혹은 협업시에 팀원이 로그인 기능을 만들 때까지 기다려야 한다면 시간이 낭비된다.
이럴때는 Mock데이터를 만들어서 자동으로 로그인되게 만드는 방법을 이용하면 좋다.

예를들면 아래 코드를 메인화면으로 가는 코드에 넣으면 자동로그인이 되어 매번 로그인 하지 않아도 된다.

User principal = userRepository.findByUsernameAndPassword("ssar", "1234");
session.setAttribute("principal", principal);



다음포스팅에서 보드컨트롤러 작성..

profile
작은것부터
post-custom-banner

0개의 댓글