TIL

[240424] 회원 기능 테스트 코드

도원좀비 2025. 4. 24. 22:16

1️⃣ UserServiceTest

회원가입, 로그인, 토큰 재발급, 로그아웃 등 로직 검증

더보기
class UserServiceTest {

    private UserService userService;
    private UserRepository userRepository;
    private PasswordEncoder passwordEncoder;
    private JwtTokenProvider jwtTokenProvider;
    private RefreshTokenService refreshTokenService;

    @BeforeEach
    void setUp() {
        userRepository = mock(UserRepository.class);
        passwordEncoder = mock(PasswordEncoder.class);
        jwtTokenProvider = mock(JwtTokenProvider.class);
        refreshTokenService = mock(RefreshTokenService.class);
        userService = new UserService(userRepository, passwordEncoder, jwtTokenProvider, refreshTokenService);
    }

    @Test
    void signup_정상동작() {
        SignupRequestDto dto = new SignupRequestDto();
        ReflectionTestUtils.setField(dto, "email", "user@email.com");
        ReflectionTestUtils.setField(dto, "password", "Password123!");
        ReflectionTestUtils.setField(dto, "nickname", "닉네임");
        ReflectionTestUtils.setField(dto, "phone", "01012345678");
        ReflectionTestUtils.setField(dto, "role", Role.ROLE_USER);
        ReflectionTestUtils.setField(dto, "socialType", SocialType.NORMAL);

        when(passwordEncoder.encode(dto.getPassword())).thenReturn("encodedPass");

        userService.signup(dto);

        verify(userRepository).checkEmailDuplicate(dto.getEmail());
        verify(userRepository).checkNicknameDuplicate(dto.getNickname());
        verify(userRepository).save(any(User.class));
    }

    @Test
    void login_성공시_토큰반환() {
        LoginRequestDto dto = new LoginRequestDto();
        ReflectionTestUtils.setField(dto, "email", "user@email.com");
        ReflectionTestUtils.setField(dto, "password", "Password123!");

        User user = User.builder()
                .id(1L)
                .email(dto.getEmail())
                .password("encoded")
                .nickname("닉네임")
                .role(Role.ROLE_USER)
                .status(UserStatus.ACTIVE)
                .socialType(SocialType.NORMAL)
                .build();

        when(userRepository.findByEmailOrThrow(dto.getEmail())).thenReturn(user);
        when(passwordEncoder.matches(dto.getPassword(), user.getPassword())).thenReturn(true);
        when(jwtTokenProvider.generateTokenPair(any(), any(), any(), any()))
                .thenReturn(new TokenResponseDto("access-token", "refresh-token"));
        when(jwtTokenProvider.getRefreshTokenDuration()).thenReturn(Duration.ofDays(7));

        TokenResponseDto token = userService.login(dto);

        assertEquals("access-token", token.getAccessToken());
        assertEquals("refresh-token", token.getRefreshToken());
        verify(refreshTokenService).save(eq(user.getId()), eq("refresh-token"), any());
    }

    @Test
    void reissue_성공시_새로운_토큰발급() {
        ReissueRequestDto dto = new ReissueRequestDto();
        ReflectionTestUtils.setField(dto, "refreshToken", "oldRefreshToken");

        when(jwtTokenProvider.getAuthorId("oldRefreshToken")).thenReturn(1L);
        when(jwtTokenProvider.getEmail("oldRefreshToken")).thenReturn("user@email.com");
        when(jwtTokenProvider.getNickname("oldRefreshToken")).thenReturn("닉네임");
        when(jwtTokenProvider.getRole("oldRefreshToken")).thenReturn(Role.ROLE_USER);
        when(refreshTokenService.get(1L)).thenReturn("oldRefreshToken");
        when(jwtTokenProvider.generateTokenPair(any(), any(), any(), any()))
                .thenReturn(new TokenResponseDto("newAccess", "newRefresh"));
        when(jwtTokenProvider.getRefreshTokenDuration()).thenReturn(Duration.ofDays(7));

        TokenResponseDto result = userService.reissue(dto);

        assertEquals("newAccess", result.getAccessToken());
        assertEquals("newRefresh", result.getRefreshToken());
        verify(refreshTokenService).save(eq(1L), eq("newRefresh"), any());
    }

    @Test
    void logout_성공시_토큰삭제() {
        when(refreshTokenService.exists(1L)).thenReturn(true);
        userService.logout(1L);
        verify(refreshTokenService).delete(1L);
    }

    @Test
    void logout_토큰없을시_예외() {
        when(refreshTokenService.exists(1L)).thenReturn(false);
        assertThrows(UserException.class, () -> userService.logout(1L));
    }
}

2️⃣ UserControllerTest

단건 조회, 회원수정, 비밀번호 변경 등 컨트롤러 기능 정상 응답 여부

더보기
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private UserService userService;
    @MockBean
    private JwtTokenProvider jwtTokenProvider;

    @Test
    @DisplayName("회원가입 요청 성공")
    void signup() throws Exception {
        SignupRequestDto request = new SignupRequestDto();
        // 필드 설정
        setField(request, "email", "test@email.com");
        setField(request, "password", "Password123!");
        setField(request, "nickname", "닉네임");
        setField(request, "phone", "01012345678");
        setField(request, "role", Role.ROLE_USER);
        setField(request, "socialType", SocialType.NORMAL);

        mockMvc.perform(post("/api/v1/users/signup")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk());

        verify(userService).signup(any(SignupRequestDto.class));
    }

    @Test
    @DisplayName("로그인 성공")
    void login() throws Exception {
        LoginRequestDto request = new LoginRequestDto();
        setField(request, "email", "user@email.com");
        setField(request, "password", "Password123!");

        when(userService.login(any())).thenReturn(new TokenResponseDto("access", "refresh"));

        mockMvc.perform(post("/api/v1/users/signin")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.accessToken").value("access"))
                .andExpect(jsonPath("$.refreshToken").value("refresh"));
    }

    @Test
    @DisplayName("토큰 재발급 성공")
    void reissue() throws Exception {
        ReissueRequestDto request = new ReissueRequestDto();
        setField(request, "refreshToken", "oldToken");

        when(userService.reissue(any())).thenReturn(new TokenResponseDto("newAccess", "newRefresh"));

        mockMvc.perform(post("/api/v1/users/reissue")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.accessToken").value("newAccess"));
    }


    @Test
    void logout() throws Exception {
        AuthUser authUser = new AuthUser(1L, "user@email.com", "nickname", Role.ROLE_USER);

        mockMvc.perform(post("/api/v1/users/logout")
                        .with(authentication(
                                new UsernamePasswordAuthenticationToken(
                                        authUser,
                                        null,
                                        List.of(new SimpleGrantedAuthority("ROLE_USER"))
                                )
                        ))
                )
                .andExpect(status().isOk());

        verify(userService).logout(1L);
    }

    @Test
    @DisplayName("회원 탈퇴 성공")
    void withdraw() throws Exception {
        WithdrawRequestDto request = new WithdrawRequestDto();
        setField(request, "password", "Password123!");

        AuthUser authUser = new AuthUser(1L, "user@email.com", "nickname", Role.ROLE_USER);

        mockMvc.perform(delete("/api/v1/users/withdraw")
                        .with(authentication(
                                new UsernamePasswordAuthenticationToken(
                                        authUser,
                                        null,
                                        List.of(new SimpleGrantedAuthority("ROLE_USER"))
                                )
                        ))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request))
                )
                .andExpect(status().isOk());

        verify(userService).withdraw(eq(1L), eq("Password123!"));
    }

}

3️⃣ RefreshTokenService

RefreshToken을 Redis에 저장, 조회, 삭제, 존재 여부 확인

더보기
class RefreshTokenServiceTest {

    private RefreshTokenService refreshTokenService;
    private StringRedisTemplate redisTemplate;
    private ValueOperations<String, String> valueOperations;

    @BeforeEach
    void setUp() {
        redisTemplate = mock(StringRedisTemplate.class);
        valueOperations = mock(ValueOperations.class);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);

        refreshTokenService = new RefreshTokenService(redisTemplate);
    }

    @Test
    void save_정상저장_호출확인() {
        Long userId = 1L;
        String token = "sample-token";
        Duration ttl = Duration.ofDays(7);

        refreshTokenService.save(userId, token, ttl);

        verify(valueOperations, times(1)).set("RT:" + userId, token, ttl);
    }

    @Test
    void get_정상조회_반환값확인() {
        Long userId = 1L;
        String expected = "stored-token";

        when(valueOperations.get("RT:" + userId)).thenReturn(expected);

        String result = refreshTokenService.get(userId);

        assertEquals(expected, result);
    }

    @Test
    void delete_정상삭제_호출확인() {
        Long userId = 1L;

        refreshTokenService.delete(userId);

        verify(redisTemplate, times(1)).delete("RT:" + userId);
    }

    @Test
    void exists_존재할때_true() {
        Long userId = 1L;

        when(redisTemplate.hasKey("RT:" + userId)).thenReturn(true);

        assertTrue(refreshTokenService.exists(userId));
    }

    @Test
    void exists_존재하지않을때_false() {
        Long userId = 1L;

        when(redisTemplate.hasKey("RT:" + userId)).thenReturn(false);

        assertFalse(refreshTokenService.exists(userId));
    }
}