종우의 삶 (전체 공개)

Spring security - 2 본문

개발/Spring

Spring security - 2

jonggae 2024. 1. 14. 19:00

대상 Repository : https://github.com/Jonggae/security

Security와 관련된 내용들을 연습한다.


 

1편에 이어 회원가입 기능에 대한 테스트코드를 작성해본다.

 

여기서 수많은 난관에 봉착한다.

 

아직 진행중이기에 결론은 나지 않았지만 어쨌든 테스트코드의 목적은 테스트를 하는 것이기에..

 

수많은 시행착오를 거쳐 아주 간단한 형태의 테스트코드가 남게 되었다. 그러나

 

package com.example.securitydemo.user.service;

import com.example.securitydemo.user.domain.User;
import com.example.securitydemo.user.dto.RegisterRequestDto;
import com.example.securitydemo.user.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;


@SpringBootTest
@Transactional
class UserServiceTest {

    @InjectMocks
    private UserService userService;
    @Mock
    private UserRepository userRepository;
    @Mock
    private PasswordEncoder passwordEncoder;

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

    @Test
    @DisplayName("유저 회원 가입 테스트")
    void registerUser() {
        //given
        RegisterRequestDto requestDto = registerRequestDto();

        //when
        userService.register(requestDto);

        //then
        verify(userRepository).save(any(User.class));


    }

    private RegisterRequestDto registerRequestDto() {
        return RegisterRequestDto.builder()
                .username("username")
                .password("password")
                .email("username@mail.com")
                .build();
    }

}

 

 

설명해보자면 단순히 회원 가입이 되는를 테스트하려는 것이기에,

 

given으로 Dto를 만들어주고

 

when으로 서비스 메서드를 실행하고, (register - 회원가입)

 

verify로 save가 되었는지 확인하는 것인데

 

자꾸 이 마지막 verify에서 문제가 발생한다.

 

Wanted but not invoked:
userRepository.save(
    <any com.example.securitydemo.user.domain.User>
);
-> at com.example.securitydemo.user.service.UserServiceTest.registerUser(UserServiceTest.java:50)
Actually, there were zero interactions with this mock.

 

대충 save가 잘 안되었다는 뜻 같은데.. 이것을 해결해보자.

난 그저 db에 회원 정보가 잘 들어가는지 확인하고 싶을 뿐이다..

 

dto로 받은 비밀번호가 null로 전달되기도 했고, 

아니면 service 레이어에서 checkInfo를 진행하는 과정에서 문제가 발생하는 것인지

뭔가 의심되는 부분이 하다보니 생겼는데 그것들을 진행해본다.

 

->> checkinfo와는 관련이 없었다. 주석처리하여도 같은 오류가 난다.

save를 시켜달라...

 


PasswordEncoder를 @Mock이 아닌 @Autowired로 변경해봤다.

자주 나왔던 해결책이었던것 같은데, 생각없이 다시 해보았다.

드디어 다른 오류메시지가 등장한다.

 

PasswordEncode를 @Mock으로 진행하면

 

이러한 간단한 암호화 테스트도 통과하지 못하는 현상이 있었다.

@Test
    @Disabled
    @DisplayName("비밀번호 암호화 테스트")
    void encryptPassword() {
        //given
        String rawPassword = "1234";

        //when
        String encodedPassword = passwordEncoder.encode(rawPassword);

        //then
        Assertions.assertAll(
                () -> Assertions.assertNotEquals(rawPassword, encodedPassword),
                () -> Assertions.assertTrue(passwordEncoder.matches(rawPassword, encodedPassword)));
    }

 

그래서 @Autowired로 주입을 해준다음 다른 테스트도 진행해봤다. (위 테스트는 통과했다.)

결국 PasswordEncoder의 문제였던 것이다...

 

위 테스트와 비슷하게 회원가입 테스트도 진행해보았다.

 

 @Test
    @DisplayName("유저 회원 가입 테스트")
    void registerUser() {
        //given
        RegisterRequestDto requestDto = registerRequestDto();
        String rawPassword = requestDto.getPassword();

        //when
        userService.register(requestDto);
        //then
        userRepository.findByEmail(requestDto.getEmail()).ifPresent(user -> {
            String encodedPassword = user.getPassword();
            System.out.println(encodedPassword);

            Assertions.assertAll(
                    () -> Assertions.assertNotEquals(rawPassword, encodedPassword),
                    () -> Assertions.assertTrue(passwordEncoder.matches(rawPassword, encodedPassword))
            );
        });
    }

        private RegisterRequestDto registerRequestDto () {
            return RegisterRequestDto.builder()
                    .username("CJW")
                    .password("1234")
                    .email("CJW@mail.com")
                    .build();
        }

 

어쨌든 로직은 비밀번호가 암호화되고, DB에 잘 들어갔다는 뜻이니.. 우선 지나가도 될 듯 하다.

 

mock을 사용하는 방법을 알게 되었는데, 다시한번 테스트 코드를 작성해보자.

 

@Mock사용 테스트코드

모킹을 사용하는 것은 어렵지 않았다. 방법을 알고나니 한번에 테스트가 통과했다는것이 넌센스였다... 몇시간을 고생했는데 ...

어찌되었든 '가짜' 객체들이기때문에 리턴될 값을 다 정해주면 되는 것이었다.

 

package com.example.securitydemo.user.service;

import com.example.securitydemo.user.dto.RegisterRequestDto;
import com.example.securitydemo.user.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;

@SpringBootTest
@Transactional
public class UserServiceTestMock {
    @InjectMocks
    private UserService userService;
    @Mock
    private UserRepository userRepository;
    @Mock
    private PasswordEncoder passwordEncoder;

    @Test
    @DisplayName("Mock 을 이용한 회원가입 테스트")
    void registerTest() {
        //given
        RegisterRequestDto requestDto = registerRequestDto();
        when(userRepository.findByUsername(requestDto.getUsername())).thenReturn(Optional.empty());
        when(userRepository.findByEmail(requestDto.getEmail())).thenReturn(Optional.empty());
        when(passwordEncoder.encode("testPassword")).thenReturn("encodedPassword");

        //when

        userService.register(requestDto);
        //then

        verify(userRepository, Mockito.times(1)).save(any());

    }

    private RegisterRequestDto registerRequestDto() {
        return RegisterRequestDto.builder()
                .username("CJW")
                .password("1234")
                .email("CJW@mail.com")
                .build();
    }
}

 

다시 보니 약간 이상하긴 한데, when 코드들을 넣지 않고 돌리는데 왜 안되나 했던 것이었다.

비어있는 mock 객체의 리턴값을 채워넣으니 오류가 발생하지 않았다. Service 코드를 진행하고, save까지 완료되어 테스트가 통과한 모습이다. 

 

userService.register에 필요한 리턴 값이 없어서 그동안 오류가 발생했던 것이다. Mock사용법을 정확히 알아두자. 필요한 것들은 전부 만들어주면 이상없이 테스트 진행이 된다.

 

맨 처음에 작성했던 테스트코드와 비교를 해보자. 차이를 알 수 있을 것.

 

'개발 > Spring' 카테고리의 다른 글

Spring security - 4  (0) 2024.01.17
Spring security - 3 // Security Config  (0) 2024.01.15
Spring Security - 1  (0) 2024.01.14
Code refactoring - 2  (1) 2024.01.10
Code refactoring - 1  (1) 2023.12.31
Comments