Loading...

Spring/Tistory / / 2022. 6. 3. 11:43

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

반응형

실제로 시큐리티가 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

 
반응형