Spring/Tistory

블로그-V3. 실제 프로젝트에 JUnit 적용(레파지토리, 서비스)

JJJAEOoni 2022. 6. 3. 11:43
반응형

실제로 시큐리티가 findByUsername을 사용한다.

비밀번호를 체크하지 않는 이유는 자기가 체크할거니까 직접 하지 말라는 것이다.

내부적으로 비밀번호를 체크한다.

 

해시로 변경하는게 레파지토리의 책임은 아니지만

만약 다른 메서드에서 오류날 수 있기 때문에 해시로 바꿔야할 때가 있을 수 있다.

 

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DataJpaTest
public class UserRepositoryTest {
    // 아무것도 의존하는게 없기 때문에 stub이 필요없음

    @Autowired
    private UserRepository userRepository; // @DataJpaTest가 메모리에 띄워줌

    @Test
    @Order(1)
    public void save_테스트() {
        // given
        String username = "ssar";
        String password = "1234"; // 해시로 암호화하는것은 서비스 책임
        String email = "ssar@nate.com";
        LocalDateTime createDate = LocalDateTime.now();
        LocalDateTime updateDate = LocalDateTime.now();

        User user = new User(null, username, password, email, null, createDate, updateDate);

        // when
        User userEntity = userRepository.save(user);

        // then
        assertEquals(username, userEntity.getUsername());
    }

    @Test
    @Order(2)
    public void findByUsername_테스트() {
        // given
        String username = "ssar";

        // when
        Optional<User> userOp = userRepository.findByUsername(username);

        if (userOp.isPresent()) {
            User user = userOp.get();

            // then
            assertEquals(username, user.getUsername());
        }

    }

    @Test
    @Order(3)
    public void findById_테스트() {
        // given
        Integer id = 1;

        // when
        Optional<User> userOp = userRepository.findById(id);

        if (userOp.isPresent()) {
            User userEntity = userOp.get();

            // then
            assertEquals(id, userEntity.getUsername());
        }
    }

    @Test
    @Order(4)
    public void findByUsernameAndEmail_테스트() {
        // given
        String username = "ssar";
        String email = "ssar@nate.com";

        // when
        Optional<User> userOp = userRepository.findByUsernameAndEmail(username, email);

        if (userOp.isPresent()) {
            User userEntity = userOp.get();

            // then
            assertEquals(username, userEntity.getUsername());
            assertEquals(email, userEntity.getEmail());
        }
    }
}

 

given, when, then 정의하는 것 : BDD Mockito

 

BDD (Behavior-Driven Development) 

given, when, then 구조의 패턴

 

스프링은 DI를 지원해주기 때문에 의존성 주입을 신경쓰지않고 객체간 의존 관계만 고민해서 설계하면 된다.

근데 의존성은 테스트 시점에 문제를 발생시킨다.

 

초기에는 시간이 좀 걸리겠지만 장기적으로 봤을때는 테스트를 하는게 더 좋다.

 

모키토는 의존성 주입을 쉽게 해주는 라이브러리

제이유닛은 단위 테스트 도구

 

하나하나 디비에 들어가는 건 레파지토리 일이고,

이런 디비 작업이 이어지는 트랜잭션을 서비스에서 테스트해야한다.

 

스텁 : 의존성 분리

 

given도 2개 stub도 2개 만들고나서 테스트해보면 nullpointerexception이 발생한다.

 

로그를 따라가보니 비크립트가 메모리에 뜨지 않았기 때문이다.

모크 컨테이터로 비크립트를 데려와야겠다.

 

@Mock
private BCryptPasswordEncoder passwordEncoder;

 

bcyrpt.encode( )에 대한 행동 정의가 없기 때문에 null 이 들어왔다,

근데 내가 얘 행동정의를 할 필요가 있을까?

 

얘가 레파지토리처럼 무거운가? 그냥 클래스 하나 메모리에 띄우는 것이다.

이걸 굳이 모크로 띄워서 stub 행동정의를 할 필요가 없다.

 

이때 붙여주는 어노테이션이 @Spy 이다.

모크 컨테이너에 스파이를 심는것이다,

 

심을 때는 바로 new 해서 넣어주면 된다.

 

@Spy
private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

 

@ExtendWith(MockitoExtension.class) // Mockito 컨테이너 생성
public class UserServiceTest {

    @InjectMocks
    private UserService userService;

    @Spy
    private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @Mock
    private UserRepository userRepository;

    @Mock
    private VisitRepository visitRepository;

    // findByUsername을 호출하는 stub을 정의할건데 이건 테스트 필요없겠다. 레파지토리에서 테스트해라.
    public void 유저네임중복체크() {}

    @Test
    public void 회원가입_테스트() {
        // given 1
        User givenUser = User.builder()
                .username("ssar")
                .password("1234")
                .email("ssar@nate.com")
                .build();

        // stub 1
        User mockUserEntity = User.builder()
                .id(1)
                .username("ssar")
                .password("1234")
                .email("ssar@nate.com")
                .profileImg(null)
                .createDate(LocalDateTime.now())
                .updateDate(LocalDateTime.now())
                .build();

        Mockito.when(userRepository.save(givenUser)).thenReturn(mockUserEntity);

        // given 2
        Visit givenVisit = Visit.builder()
                .totalCount(0L)
                .user(mockUserEntity)
                .build();

        // stub 2
        Visit visitEntity = Visit.builder()
                .id(1)
                .totalCount(0L)
                .user(mockUserEntity) // 연결
                .createDate(LocalDateTime.now())
                .updateDate(LocalDateTime.now())
                .build();

        Mockito.when(visitRepository.save(givenVisit)).thenReturn(visitEntity);

        // when 실세 서비스 호출
        User userEntity = userService.회원가입(givenUser);

        // then
        assertEquals(givenUser.getEmail(), userEntity.getEmail());
    }

    public void 프로필사진수정하기_테스트() {}

    public void 패스워드초기화() {}
}

 

 


 

본코드짜고 테스트 코드 짜다가 리팩토링을 했다.

 

1. visit 빌드패턴 정의 (@AllArgsConstructor 삭제!)

@Builder
public Visit(Integer id, Long totalCount, User user, LocalDateTime createDate, LocalDateTime updateDate) {
    this.id = id;
    this.totalCount = totalCount;
    this.user = user;
    this.createDate = createDate;
    this.updateDate = updateDate;
}

2. userService에서 회원가입 메서드에 리턴타입 바꿔주기

@Transactional
public User 회원가입(User user) {
    return userEntity;
}

 

다음시간에 컨트롤러 전부 테스트하고나면 RestDoc 라이브러리를 사용하여 API 문서를 만들어보자.

이 문서를 만들기 위한 핵심은 테스트 파일이 있어야한다. 없으면 안나온다!!

 

 

[출처]

 

https://cafe.naver.com/metacoding

 

메타코딩 : 네이버 카페

코린이들의 궁금증

cafe.naver.com

메타 코딩 유튜브

https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

 

메타코딩

문의사항 : getinthere@naver.com 인스타그램 : https://www.instagram.com/meta4pm 깃헙 : https://github.com/codingspecialist 유료강좌 : https://www.easyupclass.com

www.youtube.com

 
반응형