본문 바로가기

Dot Programming/Spring Clone

[스프링 웹앱 프로젝트 #7]회원가입 : 리팩토링 및 테스트

#7 회원가입 리팩토링 및 테스트

리팩토링 하기 전에 테스트 코드를 먼저 작성하자.
→ 그래야 코드를 변경한 이후에 불안하지 않다.
 변경한 코드가 무언가를 깨트리지 않았다는 것을 확인할 수 있다.

테스트 할 것
 폼에 이상한 값들이 들어간 경우에 다시 폼이 보여지는가?
 폼에 값이 정상적인 경우
    가입한 회원 데이터가 존재하는가?
     이메일이 보내지는가?

리팩토링
 메소드가 너무 길지 않은가?
 코드를 읽기 쉬운가?
     내가 작성한 코드를 내가 읽기 어렵다면 남들에겐 훨씬 더 어렵다.
 코드가 적절한 위치에 있는가?
     객체들 사이의 의존 관계
     책임이 너무 많지는 않는지

 

테스트 코드 작성하기

1. 회원가입 처리 - 입력값 오류 403 error

이메일, 비밀번호 입력값 오류 → isOk() /  view().name(account/sign-up)

 

입력값 오류 발생시

 

현재 SecurityConfig에 authorizeRequests만 설정한 상태이다.

 

 

 

403에러 발생 이유는 CSRF(Cross Site Request Forgery)때문이다. 

CSRF?
타 사이트로 공격 대상 사이트의 Form데이터를 보내는 것이다.
ex. 은행계좌이체 데이터를 은행 사이트가 아닌 타사이트로 보내는 것이다.

 

이를 방지하기 위해 Security에서 CSRF token을 자동으로 사용한다.

hidden값을 지닌 csrf토큰

 

데이터에 CSRF토큰이 다르거나 제공이 안되면 403 에러가 난다. 

좋지 않은 Form데이터이기 때문이다. 인증하지 않아도 사용해도 되지만 안전하지 않은 데이터는 사용하는 것이 아니다. 그래서 Form데이터에 with(csrf())를 같이 넣어서 보내주면 된다

 

with(csrf())

 

2. 회원가입 처리 - 올바른 값 입력

이메일 비밀번호 입력값 정상 입력 → is3xxRedirection() / view().name("redirect:/")

 @DisplayName("회원 가입 처리 - 입력값 오류")
     @Test
    void signUpSubmit_with_wrong_input() throws Exception{
        mockMvc.perform(post("/sign-up")
                .param("nickname", "jongwon")
                .param("email", "jong9712@naver.com")
                .param("password", "12345678")
                .with(csrf()))
//                .andExpect(status().isOk())
//                .andExpect(view().name("account/sign-up"))
                //--올바른 데이터를 입력했을 경우 redirect//
                .andExpect(status().is3xxRedirection())
                .andExpect(view().name("redirect:/"));

        assertTrue(accountRepository.existsByEmail("jong9712@naver.com"));
        then(javaMailSender).should().send(any(SimpleMailMessage.class));

     }

 

 

 

코드 리팩토링

메소드를 빼면서 메소드 이름만 잘 지으면 코드 읽기가 편해짐.

Controller가 하는 일이 많아지다보면 대부분의 의존성을 가지게 됨 (Service쪽으로 무게를 덜어줘야함) 

 

AccountService.class 생성

@Service
@RequiredArgsConstructor
public class AccountService {

    private final AccountRepository accountRepository;
    private final JavaMailSender javaMailSender;

    public void processNewAccount(SignUpForm signUpForm) {
        Account newAccount = saveNewAccount(signUpForm);
        //이메일 인증 토큰 생성
        newAccount.generateEmailCheckToken();
        
        sendSignUpConfirmEmail(newAccount);
    }


    private Account saveNewAccount(@Valid SignUpForm signUpForm) {
        Account account = Account.builder()
                .email(signUpForm.getEmail())
                .nickname(signUpForm.getNickname())
                .password(signUpForm.getPassword()) // TODO : 인코딩 필요
                .studyCreatedByWeb(true)
                .studyEnrollmentResultByWeb(true)
                .studyUpdatedByWeb(true)
                .build();

        return accountRepository.save(account);
    }

    private void sendSignUpConfirmEmail(Account newAccount) {
        //이메일 전송
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setTo(newAccount.getEmail());
        mailMessage.setSubject("닷 스터디, 회원 가입 인증");
        mailMessage.setText("/check-email-token?token="+ newAccount.getEmailCheckToken() +
                "&email=" + newAccount.getEmail());
        javaMailSender.send(mailMessage);
    }

}

 

 

AccountController.class 

@Slf4j
@Controller
@RequiredArgsConstructor
public class AccountController {

    private final SignUpFormValidator signUpFormValidator;
    private final AccountService accountService;

    /**
     * SignUpForm데이터를 받을때 바인더를 설정
     * public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors)
     * @Valid SignUpForm이랑 매핑
     */
    @InitBinder("signUpForm")
    public void initBinder(WebDataBinder webDataBinder){
        webDataBinder.addValidators(signUpFormValidator);
    }


    @GetMapping("/sign-up")
    public String signUpForm(Model model){
//        model.addAttribute("signUpForm", new SignUpForm());
//        아래와 동일 "signUpForm" -> 동일한 이름으로 알아서 찾아감
        model.addAttribute(new SignUpForm());
        return "account/sign-up";
    }

    @PostMapping("/sign-up")
    public String signUpSubmit(@Valid SignUpForm signUpForm, Errors errors){
        if (errors.hasErrors()){
            return "account/sign-up";
        }

        accountService.processNewAccount(signUpForm);

        // TODO : 회원가입 처리
        return "redirect:/";

    }



}

 


참고

인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발