์ด๋ฒ์๋ ํ์ ์๋น์ค์์ ๋ฐ์ํ ์ ์๋ ์์ธ๋ค์ MemberException์ผ๋ก ๋ชจ์, ๊ณตํต์ผ๋ก ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค.
์์ธ์ฒ๋ฆฌ๋ @ControllerAdvice๋ฅผ ํตํด, ํ๊ณณ์์ ์์ธ๋ฅผ ๊ด๋ฆฌํด์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค.
- ์ํ๋ฆฌํฐ๋ฅผ ์ด์ฉํ JSON ๋ฐ์ดํฐ๋ก ๋ก๊ทธ์ธ (์๋ฃ)
- JWT๋ฅผ ์ด์ฉํ ์ธ์ฆ (์๋ฃ)
- ๋๋ฉ์ธ, ํ ์ด๋ธ ์ค๊ณ, ์ํฐํฐ ์์ฑ (์๋ฃ)
- ๋๊ธ ์ญ์ ๋ก์ง ๊ตฌํ (์๋ฃ)
- ํ์๊ฐ์ + ์ ๋ณด์์ ๋ฑ ํ์ ์๋น์ค ๊ตฌํ (์๋ฃ)
- ๊ฒ์ํ ์๋น์ค ๊ตฌํ
- ๋๊ธ ์๋น์ค ๊ตฌํ (1๋๊ธ -> *(๋ฌดํ) ๋๋๊ธ ๊ตฌ์กฐ)
- ์์ธ ์ฒ๋ฆฌ (์งํ ์ค)
- ์์ธ ๋ฉ์ธ์ง ๊ตญ์ ํ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๊ฒ์ํ ๋ถ๋ฅ
- ๊ฒ์๊ธ ํ์ด์ง
- ๋์ ์ธ ๊ฒ์ ์กฐ๊ฑด์ ์ฌ์ฉํ ๊ฒ์
- ์ฌ์ฉ์ ๊ฐ ์ชฝ์ง ๊ธฐ๋ฅ
- ๋ฌดํ ์ชฝ์ง ์คํฌ๋กค
- ๊ฒ์๋ฌผ & ๋๊ธ์ ๋ํ ์๋
- ์ชฝ์ง์ ๋ํ ์๋
- ์ ์ํ ์ฌ์ฉ์ ๊ฐ ์ค์๊ฐ ์ฑํ
- ํ์๊ฐ์ ์ ๊ฒ์ฆ(์: XX๋ํ๊ต XX๊ณผ๊ฐ ์๋๋ฉด ๊ฐ์ ํ ์ ์๊ฒ)
- Swagger๋ฅผ ์ฌ์ฉํ API ๋ฌธ์ ๋ง๋ค๊ธฐ
- ์ ๊ณ & ๋ธ๋๋ฆฌ์คํธ ๊ธฐ๋ฅ
- AOP๋ฅผ ํตํ ๋ก๊ทธ
- ์ด๋๋ฏผ ํ์ด์ง
- ์บ์
- ๋ฐฐํฌ (+ ๋ฌด์ค๋จ ๋ฐฐํฌ)
- ๋ฐฐํฌ ์๋ํ
- ํฌํธ์ ์ด๋ํฐ ์ค๊ณ๋ฅผ ๋ฐ๋ฅด๋ ํจํค์ง ๊ตฌ์กฐ ์ค๊ณํ๊ธฐ
- ...
BaseException
BaseException์ ์์ผ๋ก ์ ์ํ ๋ชจ๋ ์ปค์คํ ์์ธ์ ๋ถ๋ชจ ํด๋์ค๋ก, ์์ผ๋ก ์์ฑํ ์ปค์คํ ์์ธ ํด๋์ค๋ค์ BaseException ํ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ํ๊ธฐ ์ํด์ ๋ง๋ค์ด์ฃผ์์ต๋๋ค.
public abstract class BaseException extends RuntimeException{
public abstract BaseExceptionType getExceptionType();
}
RuntimeException์ ์์๋ฐ์ ์ถ์ ํด๋์ค๋ก ์ค์ ํด์ฃผ์์ต๋๋ค.
BaseException์ BaseExceptionType์ ๋ฐํํ๋ getExceptionType ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ์ด๋ ์ดํ Enum์ผ๋ก ์ค์ ํ์ฌ ์๋ฌ ๋ฉ์ธ์ง์ Http ์ํ์ฝ๋, ๊ทธ๋ฆฌ๊ณ ์ ํฌ๋ง์ ์๋ฌ์ฝ๋๋ฅผ ์ค์ ํด์ฃผ์ด ๊ด๋ฆฌํด์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค.
BaseExceptionType
public interface BaseExceptionType {
int getErrorCode();
HttpStatus getHttpStatus();
String getErrorMessage();
}
์๋ฌ์ฝ๋์ Http์ํ, ๊ทธ๋ฆฌ๊ณ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๊ฐ์ง๊ณ ์๋๋ก ๋ง๋ค์ด ์ฃผ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด getter ๋ฉ์๋๋ฅผ ์ค์ ํ์ต๋๋ค.
์ด์ Member์ ๋ํ ์์ธ์ฒ๋ฆฌ๋ฅผ ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
MemberException
public class MemberException extends BaseException {
private BaseExceptionType exceptionType;
public MemberException(BaseExceptionType exceptionType) {
this.exceptionType = exceptionType;
}
@Override
public BaseExceptionType getExceptionType() {
return exceptionType;
}
}
์ฝ๋๋ ๋ณ๊ฑฐ ์์ต๋๋ค.
BaseExceptionType์ ๋ฉค๋ฒ๋ณ์๋ก ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์์ฑ์๋ฅผ ํตํด ์์ฑํ๋ ์๊ฐ ExceptionType์ ์ค์ ํ๋๋ก ๋ง๋ค์ด ์ฃผ์์ต๋๋ค.
MemberExceptionType
public enum MemberExceptionType implements BaseExceptionType {
//== ํ์๊ฐ์
, ๋ก๊ทธ์ธ ์ ==//
ALREADY_EXIST_USERNAME(600, HttpStatus.OK, "์ด๋ฏธ ์กด์ฌํ๋ ์์ด๋์
๋๋ค."),
WRONG_PASSWORD(601,HttpStatus.OK, "๋น๋ฐ๋ฒํธ๊ฐ ์๋ชป๋์์ต๋๋ค."),
NOT_FOUND_MEMBER(602, HttpStatus.OK, "ํ์ ์ ๋ณด๊ฐ ์์ต๋๋ค.");
private int errorCode;
private HttpStatus httpStatus;
private String errorMessage;
MemberExceptionType(int errorCode, HttpStatus httpStatus, String errorMessage) {
this.errorCode = errorCode;
this.httpStatus = httpStatus;
this.errorMessage = errorMessage;
}
@Override
public int getErrorCode() {
return this.errorCode;
}
@Override
public HttpStatus getHttpStatus() {
return this.httpStatus;
}
@Override
public String getErrorMessage() {
return this.errorMessage;
}
}
enum์ผ๋ก ์์ฑํด ์ฃผ์์ต๋๋ค.
์์ง์ ์ค๋ฅ๊ฐ 3์ข ๋ฅ๋ฐ์ ์์ง๋ง, ์ดํ ๋ ์ถ๊ฐ๋ ์๋ ์์ต๋๋ค.
์๋ฌ์ฝ๋์ Http ์ํ์ฝ๋, ๊ทธ๋ฆฌ๊ณ ์๋ฌ ๋ฉ์ธ์ง๊ฐ ์กด์ฌํฉ๋๋ค.
๊ทธ๋ฌ๋ ์ ํฌ๋ Http ์ํ์ฝ๋๋ 200์ผ๋ก ํต์ผํ ๊ฒ์ด๊ธฐ์ Http.OK๋ก ํต์ผ์์ผ ์ฃผ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด ๊ตณ์ด ๋ฃ์ด์ฃผ์ง ์์๋ 200์ผ๋ก ์ฒ๋ฆฌํ๋ฉด ๋๋๊ฑฐ ์๋๋ ํ์ค ์๋ ์๋๋ฐ,
ํน์ ์ธ์ ์ ๊ฐ ๋ง์์ด ๋ฐ๋์ด์ ์ํ์ฝ๋๋ฅผ ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ ์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์, ๋ณ๊ฒฝ์ ๋๋นํ๊ธฐ ์ํด ๋ฃ์ด์ฃผ์์ต๋๋ค.
์ด์ MemberException์ ์ค์ ๋์๊ณ ์ด๋ฅผ ์ฝ๋์ ์ ์ฉ์์ผ๋ณด๊ฒ ์ต๋๋ค.
MemberServiceImpl ๋ณ๊ฒฝ
Exception์ ๋ฐ์์ํค๋ ์์ธ๋ฅผ ๋ชจ๋ MemberException์ผ๋ก ๋ณ๊ฒฝํด ์ฃผ์์ต๋๋ค.
@Service
@RequiredArgsConstructor
@Transactional
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
@Override
public void signUp(MemberSignUpDto memberSignUpDto) throws Exception {
Member member = memberSignUpDto.toEntity();
member.addUserAuthority();
member.encodePassword(passwordEncoder);
if(memberRepository.findByUsername(memberSignUpDto.username()).isPresent()){
throw new MemberException(MemberExceptionType.ALREADY_EXIST_USERNAME);
}
memberRepository.save(member);
}
@Override
public void update(MemberUpdateDto memberUpdateDto) throws Exception {
Member member = memberRepository.findByUsername(SecurityUtil.getLoginUsername()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
memberUpdateDto.age().ifPresent(member::updateAge);
memberUpdateDto.name().ifPresent(member::updateName);
memberUpdateDto.nickName().ifPresent(member::updateNickName);
}
@Override
public void updatePassword(String checkPassword, String toBePassword) throws Exception {
Member member = memberRepository.findByUsername(SecurityUtil.getLoginUsername()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
if(!member.matchPassword(passwordEncoder, checkPassword) ) {
throw new MemberException(MemberExceptionType.WRONG_PASSWORD);
}
member.updatePassword(passwordEncoder, toBePassword);
}
@Override
public void withdraw(String checkPassword) throws Exception {
Member member = memberRepository.findByUsername(SecurityUtil.getLoginUsername()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
if(!member.matchPassword(passwordEncoder, checkPassword) ) {
throw new MemberException(MemberExceptionType.WRONG_PASSWORD);
}
memberRepository.delete(member);
}
@Override
public MemberInfoDto getInfo(Long id) throws Exception {
Member findMember = memberRepository.findById(id).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
return new MemberInfoDto(findMember);
}
@Override
public MemberInfoDto getMyInfo() throws Exception {
Member findMember = memberRepository.findByUsername(SecurityUtil.getLoginUsername()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
return new MemberInfoDto(findMember);
}
}
ExceptionAdvice ๋ณ๊ฒฝ
๋ง์ง๋ง์ ๋๋ค. ์ปจํธ๋กค๋ฌ์์ ๋ฐ์ํ MemberException์ ํฌํจํด์, ์ดํ ๋ฐ์ํ ์ปค์คํ ์์ธ๋ค์ ์ฒ๋ฆฌํด์ค ์ ์๋๋ก ExceptionAdvice๋ฅผ ๋ณ๊ฒฝํด ์ฃผ๊ฒ ์ต๋๋ค.
@RestControllerAdvice
@Slf4j
public class ExceptionAdvice {
@ExceptionHandler(BaseException.class)
public ResponseEntity handleBaseEx(BaseException exception){
log.error("BaseException errorMessage(): {}",exception.getExceptionType().getErrorMessage());
log.error("BaseException errorCode(): {}",exception.getExceptionType().getErrorCode());
return new ResponseEntity(new ExceptionDto(exception.getExceptionType().getErrorCode()),exception.getExceptionType().getHttpStatus());
}
@ExceptionHandler(Exception.class)
public ResponseEntity handleMemberEx(Exception exception){
exception.printStackTrace();
return new ResponseEntity(HttpStatus.OK);
}
@Data
@AllArgsConstructor
static class ExceptionDto {
private Integer errorCode;
}
}
(์๋ฒ์์ ์์ธ๊ฐ ๋ฐ์ํ๋๋ผ๋, ์ํ์ฝ๋๋ 200์ ๋ฐํํ๋๋ก ์ค์ ํ์ต๋๋ค.)
ํ ์คํธ์ฝ๋ ์์
ํ์ฌ MemberException์ ์ค์ ํด ์ค์ผ๋ก์จ, ๊ณ ์ฅ๋ ํ ์คํธ์ฝ๋๋ค์ ๊ณ ์ณ์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ฐธ๊ณ ๋ก ์์ง ์ ๊ทผ ๊ถํ์ด ์๋ ํ์ด์ง์ ์ ๊ทผํ์ ๋์ ์๋ ํ์ด์ง์ ์ ๊ณตํ์ ๋๋ 403, 404๋ฅผ ๋ฐ์์ํต๋๋ค.
์๋๋ ์ด๋ ๋ค 200์ผ๋ก ์ฒ๋ฆฌํ๋ ค ํ์ผ๋, ๋๋ถ๋ถ์ ์ฌ์ดํธ์์ ์๋ ์ฃผ์๋ก ์ ๊ทผํ์ ๊ฒฝ์ฐ 200์ ๋ฟ๋ฆฌ๋ ๊ฒฝ์ฐ๋ ๋ณด์ง ๋ชปํ์์ต๋๋ค.
๋ฐ๋ผ์ ์ด๋ 403, 404 ๊ทธ๋๋ก ์ฒ๋ฆฌํ๋๋ก ๋ ๋๋๋ก ํ๊ฒ ์ต๋๋ค.
MemberControllerTest ์์
@Test
public void ํ์์ ๋ณด์กฐํ_์คํจ_์๋ํ์์กฐํ() throws Exception {
//given
String signUpData = objectMapper.writeValueAsString(new MemberSignUpDto(username, password, name, nickName, age));
signUp(signUpData);
String accessToken = getAccessToken();
//when
MvcResult result = mockMvc.perform(
get("/member/2211")
.characterEncoding(StandardCharsets.UTF_8)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk()).andReturn();
//then TODO: ์ฌ๊ธฐ ์ค๋ฅ๋จ, ์ํ์ฝ๋ 600 ๋ฐํํจ, ๊ณ ์น๊ธฐ
assertThat(result.getResponse().getContentAsString()).isEqualTo("");//๋น ๋ฌธ์์ด
}
๋ค์ ํ ์คํธ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ๋ณ๊ฒฝํด์ค๋๋ค.
@Test
public void ํ์์ ๋ณด์กฐํ_์คํจ_์๋ํ์์กฐํ() throws Exception {
//given
String signUpData = objectMapper.writeValueAsString(new MemberSignUpDto(username, password, name, nickName, age));
signUp(signUpData);
String accessToken = getAccessToken();
//when
MvcResult result = mockMvc.perform(
get("/member/2211")
.characterEncoding(StandardCharsets.UTF_8)
.header(accessHeader, BEARER + accessToken))
.andExpect(status().isOk()).andReturn();
//then
Map<String, Integer> map = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
assertThat(map.get("errorCode")).isEqualTo(MemberExceptionType.NOT_FOUND_MEMBER.getErrorCode());//๋น ๋ฌธ์์ด
}
MemberServiceTest ๋ณ๊ฒฝ
@Test
public void ํ์๊ฐ์
_์ฑ๊ณต() throws Exception {
//given
MemberSignUpDto memberSignUpDto = makeMemberSignUpDto();
//when
memberService.signUp(memberSignUpDto);
clear();
//then TODO : ์ฌ๊ธฐ MEMBEREXCEPTION์ผ๋ก ๊ณ ์น๊ธฐ
Member member = memberRepository.findByUsername(memberSignUpDto.username()).orElseThrow(() -> new Exception("ํ์์ด ์์ต๋๋ค"));
assertThat(member.getId()).isNotNull();
assertThat(member.getUsername()).isEqualTo(memberSignUpDto.username());
assertThat(member.getName()).isEqualTo(memberSignUpDto.name());
assertThat(member.getNickName()).isEqualTo(memberSignUpDto.nickName());
assertThat(member.getAge()).isEqualTo(memberSignUpDto.age());
assertThat(member.getRole()).isSameAs(Role.USER);
}
์์ ๋ฉ์๋๋ฅผ ๋ณ๊ฒฝํด์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค
์๋์ ๊ฐ์ด ๋ฐ๊พธ์ด์ค๋๋ค.
@Test
public void ํ์๊ฐ์
_์ฑ๊ณต() throws Exception {
//given
MemberSignUpDto memberSignUpDto = makeMemberSignUpDto();
//when
memberService.signUp(memberSignUpDto);
clear();
//then
Member member = memberRepository.findByUsername(memberSignUpDto.username()).orElseThrow(() -> new MemberException(MemberExceptionType.NOT_FOUND_MEMBER));
assertThat(member.getId()).isNotNull();
assertThat(member.getUsername()).isEqualTo(memberSignUpDto.username());
assertThat(member.getName()).isEqualTo(memberSignUpDto.name());
assertThat(member.getNickName()).isEqualTo(memberSignUpDto.nickName());
assertThat(member.getAge()).isEqualTo(memberSignUpDto.age());
assertThat(member.getRole()).isSameAs(Role.USER);
}
๋ค์ ๋ฉ์๋๋ ๋ฐ๊ฟ์ฃผ๋๋ก ํ๊ฒ ์ต๋๋ค.
@Test
public void ํ์ํํด_์คํจ_๋น๋ฐ๋ฒํธ๊ฐ_์ผ์นํ์ง์์() throws Exception {
//given
MemberSignUpDto memberSignUpDto = setMember();
//when, then TODO : MemberException์ผ๋ก ๊ณ ์ณ์ผ ํจ
assertThat(assertThrows(Exception.class ,() -> memberService.withdraw(PASSWORD+"1")).getMessage()).isEqualTo("๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
}
์๋์ ๊ฐ์ด ๋ฐ๊พธ์ด์ค๋๋ค.
@Test
public void ํ์ํํด_์คํจ_๋น๋ฐ๋ฒํธ๊ฐ_์ผ์นํ์ง์์() throws Exception {
//given
MemberSignUpDto memberSignUpDto = setMember();
//when, then
assertThat(assertThrows(MemberException.class ,() -> memberService.withdraw(PASSWORD+"1")).getExceptionType()).isEqualTo(MemberExceptionType.WRONG_PASSWORD);
}
๋ง์ง๋ง์ ๋๋ค
@Test
public void ํ์๊ฐ์
_์คํจ_์์ธ_์์ด๋์ค๋ณต() throws Exception {
//given
MemberSignUpDto memberSignUpDto = makeMemberSignUpDto();
memberService.signUp(memberSignUpDto);
clear();
//when, then TODO : MemberException์ผ๋ก ๊ณ ์ณ์ผ ํจ
assertThat(assertThrows(Exception.class, () -> memberService.signUp(memberSignUpDto)).getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ์์ด๋์
๋๋ค.");
}
์๋์ ๊ฐ์ด ๋ฐ๊พธ์ด์ค๋๋ค
@Test
public void ํ์๊ฐ์
_์คํจ_์์ธ_์์ด๋์ค๋ณต() throws Exception {
//given
MemberSignUpDto memberSignUpDto = makeMemberSignUpDto();
memberService.signUp(memberSignUpDto);
clear();
//when, then
assertThat(assertThrows(MemberException.class, () -> memberService.signUp(memberSignUpDto)).getExceptionType()).isEqualTo(MemberExceptionType.ALREADY_EXIST_USERNAME);
}
์ด๋ ๊ฒ ํด์ ์์ธ์ฒ๋ฆฌ๊น์ง ๋ค๋ฃจ์ด๋ณด์์ต๋๋ค.
๋ค์ ์๊ฐ์๋ Member ์ํฐํฐ์ ์ด์ด์ Post์ Comment์ ๋ํ ์๋น์ค ๋ก์ง์ ์์ฑํ๋๋ก ํ๊ฒ ์ต๋๋ค.
๊ฐ์ฌํฉ๋๋ค
์ ์ฒด ์ฝ๋๋ ๊นํ๋ธ์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
https://github.com/ShinDongHun1/SpringBoot-Board-API