JWT를 이용한 로그인 구현 (2) - 엔티티 설계

회원 엔티티

@Entity
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(nullable = false, unique = true)
    private String email; // 아이디

    @Column(nullable = false)
    private String loginPw; // 비밀번호

    @Column(nullable = false)
    private String verifiedPw; // 비밀번호 확인

    @Column(nullable = false, unique = true)
    private String name; // 이름

    private int age; // 나이

    @Enumerated(EnumType.STRING)
    @Column(nullable = false, length = 30)
    private RoleType role; // 권한

    /* 패스워드 암호화 관련 */
    public void encodePassword(PasswordEncoder passwordEncoder){
        this.loginPw = passwordEncoder.encode(loginPw);
    }

    /* 권한 부여 */
    public void addMemberAuthority() {
        this.role = RoleType.USER;
    }
}

 

public enum RoleType {
    USER, ADMIN
}

 

Member 엔티티는 위와 같습니다.

사용자는 회원가입을 진행하기 위해 이메일, 비밀번호, 비밀번호 확인, 이름, 나이를 입력해야 합니다.

 

회원 리포지토리

public interface MemberRepository extends JpaRepository<Member, Long> {
    boolean existsByEmail(String email);
    boolean existsByName(String name);
    Optional<Member> findByEmail(String email);
}

 

회원 서비스

역할과 사용을 분리하기 위해 MemberService 인터페이스를 구현한 후, MemberServiceImpl이 해당 인터페이스를 구현합니다.

public interface MemberService {
    boolean confirmEmail(String email);
    boolean confirmName(String name);
    void createMember(SignupRequestDTO signupRequestDTO);
    Optional<Member> findByEmail(String email);
}

 

@Service
@RequiredArgsConstructor
@Transactional
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;

    @Override
    public boolean confirmEmail(String email) {
        return memberRepository.existsByEmail(email);
    }

    @Override
    public boolean confirmName(String name) {
        return memberRepository.existsByName(name);
    }

    @Override
    public void createMember(SignupRequestDTO signupRequestDTO) {
        Member member = signupRequestDTO.toEntity();
        member.addMemberAuthority();
        member.encodePassword(passwordEncoder);

        if(findByEmail(signupRequestDTO.getEmail()).isPresent()) {

        }
    }

    @Override
    public Optional<Member> findByEmail(String email) {
        return Optional.empty();
    }
}

 

회원 컨트롤러

@RestController
@RequiredArgsConstructor
@RequestMapping("/member")
public class MemberController {

    private final MemberServiceImpl memberServiceImpl;

    @GetMapping("/confirmEmail/{email}")
    public ApiResult<?> confirmEmail(@PathVariable("email") String email) {
        if (memberServiceImpl.confirmEmail(email)) {
            throw new MemberException(ErrorStatus.MEMBER_EMAIL_ALREADY_EXISTS);
        }
        return ApiResult.onSuccess();
    }

    @GetMapping("/confirmName/{name}")
    public ApiResult<?> confirmName(@PathVariable("name") String name) {
        if (memberServiceImpl.confirmName(name)) {
            throw new MemberException(ErrorStatus.MEMBER_NAME_ALREADY_EXISTS);
        }
        return ApiResult.onSuccess();
    }

    @PostMapping("/signup")
    public ApiResult<?> signup(@RequestBody SignupRequestDTO requestDTO) {
        SignupResponseDTO responseDTO = memberServiceImpl.createMember(requestDTO);
        return ApiResult.onSuccess(responseDTO);
    }
}

 

회원 서비스 테스트

class MemberServiceImplTest {

    @Mock
    private MemberRepository memberRepository;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks
    private MemberServiceImpl memberServiceImpl;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @DisplayName("이메일 중복 확인")
    void confirmEmail() {
        String email = "test@example.com";
        when(memberRepository.existsByEmail(email)).thenReturn(true);

        boolean result = memberServiceImpl.confirmEmail(email);

        assertTrue(result);
        verify(memberRepository, times(1)).existsByEmail(email);
    }

    @Test
    @DisplayName("이름(닉네임) 중복 확인")
    void confirmName() {
        String name = "testname";
        when(memberRepository.existsByName(name)).thenReturn(true);

        boolean result = memberServiceImpl.confirmName(name);

        assertTrue(result);
        verify(memberRepository, times(1)).existsByName(name);
    }

    @Test
    @DisplayName("회원가입")
    void createMember() {
        SignupRequestDTO signupRequestDTO = SignupRequestDTO.builder()
                .email("test@example.com")
                .loginPw("password123")
                .verifiedPw("password123")
                .name("testname")
                .age(25)
                .build();

        when(passwordEncoder.encode(any(String.class))).thenReturn("encodedPassword");

        SignupResponseDTO responseDTO = memberServiceImpl.createMember(signupRequestDTO);

        assertEquals(signupRequestDTO.getEmail(), responseDTO.getEmail());
        assertEquals("encodedPassword", responseDTO.getLoginPw());
        assertEquals(signupRequestDTO.getName(), responseDTO.getName());
        assertEquals(signupRequestDTO.getAge(), responseDTO.getAge());
        verify(memberRepository, times(1)).save(any(Member.class));
    }

    @Test
    void findByEmail() {
        String email = "test@example.com";
        Member member = Member.builder().email(email).build();
        when(memberRepository.findByEmail(email)).thenReturn(Optional.of(member));

        Optional<Member> result = memberServiceImpl.findByEmail(email);

        assertTrue(result.isPresent());
        assertEquals(email, result.get().getEmail());
        verify(memberRepository, times(1)).findByEmail(email);
    }
}

 

회원 컨트롤러 테스트

class MemberControllerTest {

    @Mock
    private MemberServiceImpl memberServiceImpl;

    @InjectMocks
    private MemberController memberController;

    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(memberController)
                .setControllerAdvice(new MemberHandler())
                .build();
    }

    @Test
    @DisplayName("이메일 중복 확인")
    void confirmEmail() throws Exception {
        String email = "test@example.com";
        when(memberServiceImpl.confirmEmail(email)).thenReturn(true);

        mockMvc.perform(get("/member/confirmEmail/{email}", email))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value(ErrorStatus.MEMBER_EMAIL_ALREADY_EXISTS.getCode()))
                .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_EMAIL_ALREADY_EXISTS.getMessage()));
    }

    @Test
    @DisplayName("이름 중복 확인")
    void confirmName() throws Exception {
        String name = "testname";
        when(memberServiceImpl.confirmName(name)).thenReturn(true);

        mockMvc.perform(get("/member/confirmName/{name}", name))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value(ErrorStatus.MEMBER_NAME_ALREADY_EXISTS.getCode()))
                .andExpect(jsonPath("$.message").value(ErrorStatus.MEMBER_NAME_ALREADY_EXISTS.getMessage()));
    }

    @Test
    @DisplayName("회원가입")
    void signup() throws Exception {
        SignupRequestDTO requestDTO = SignupRequestDTO.builder()
                .email("test@example.com")
                .loginPw("password123")
                .verifiedPw("password123")
                .name("testname")
                .age(25)
                .build();

        SignupResponseDTO responseDTO = new SignupResponseDTO(requestDTO.toEntity());

        when(memberServiceImpl.createMember(any(SignupRequestDTO.class))).thenReturn(responseDTO);

        mockMvc.perform(post("/member/signup")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"email\":\"test@example.com\", \"loginPw\":\"password123\", \"verifiedPw\":\"password123\", \"name\":\"testname\", \"age\":25}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.email").value("test@example.com"))
                .andExpect(jsonPath("$.result.name").value("testname"))
                .andExpect(jsonPath("$.result.age").value(25));
    }
}

 

다음 글에서는 본격적으로 JWT 기반 로그인을 구현할 예정입니다.

 

'⚙️ Backend > Spring' 카테고리의 다른 글

JWT를 이용한 로그인 구현 (1) - 프로젝트 생성  (0) 2024.06.23