Spring

JUnit5 Service 테스트

JJJAEOoni 2022. 5. 30. 12:49
반응형

서비스가 레파지토리에 의존적이라 레파지토리까지 IoC에 띄워야할까?

 

띄워야한다.

 

하지만 실제 db에 값이 들어가고 안들어가고는 테스트할 필요가 없다.

레파지토리 테스트에서 이미 확인했기 때문이다.

 

이때 사용하는게 Mock이다. 가짜 데이터를 띄워주는것이다.

 

레파지토리 테스트할 때 @ExtendWith을 붙여주지 않은 이유는 @DataJpaTest에 포함되어있기 때문이다.

 

@ExtendWith에 SpringExtension.class를 붙이면 모든게 다 메모리에 떠버리기 때문에

서비스에서는 MockitoExtension.class를 붙여준다.

 

@ExtendWith(MockitoExtension.class) // 스프링 컨테이너 내부에 있는것처럼 속이기!
public class BookServiceTest {

}

 

붙이면 내가 지금 스프링 컨테이너 내부에 있다고 속일 수 있어서 IoC에 접근이 가능하다.

 

서비스의 given은 dto이다.

이걸 컨트롤러를 만들어서 전달해줄게 아니니까 내가 강제로 가짜 데이터(given)를 만들어준다.

 

@ExtendWith(MockitoExtension.class) // 스프링 컨테이너 내부에 있는것처럼 속이기!
public class BookServiceTest {

    @Test
    public void 책등록하기_테스트() {
        // given
        BookSaveReqDto reqDto = new BookSaveReqDto();
        reqDto.setTitle("스프링1강");
        reqDto.setAuthor("메타코딩");
        
        // when
        
        // then
    }
}

 

given이 끝났으니 save를 테스트할건데 직접 save하진 않을것이다.

레파지토리 영역까지 테스트하진 않을거니까. 분리하자!

 

하지만 레파지토리까지 테스트해도 상관없다.

다만 느려지고 분리가 되지 않을뿐이지!

 

가짜로 save하는 방법이 있다.

 

@Extendwith로 스프링 내부에 들어와있는 척 하는 중이라

BookRepository를 IoC에 띄운적이 없기 때문에 의존성 주입이 불가능하다.

이때 @Mock를 붙여준다.

 

그러면 실제로 저장되지 않고 가짜로 저장시켜준다.

 

@ExtendWith(MockitoExtension.class) // 스프링 컨테이너 내부에 있는것처럼 속이기!
public class BookServiceTest {

    @Mock
    private BookRepository bookRepository;

    @Test
    public void 책등록하기_테스트() {
        // given
        BookSaveReqDto reqDto = new BookSaveReqDto();
        reqDto.setTitle("스프링1강");
        reqDto.setAuthor("메타코딩");
        
        // when

        // then
    }
}

 

얘는 가짜로 save한 것이기 때문에 save를 리턴받아보면 null을 돌려준다. 

아무동작도 안하고 테스트만 통과할 수 있게 가짜 객체를 만들어준것이다.

 


 

스텁 stub 행동정의 = 가설

 

어차피 bookRepository가 Mockito라서 null을 리턴해주니까

얘가 리턴해줄때 이런값을 리턴해줄거야! 하고 가설을 정의하는것이다.

 

1. 서비스 테스트는 실제 서비스 메서드를 호출해야한다.

그 메서드 안에서 bookRepository.save( )가 동작할 때 stub이 발동한다.

 

근데 BookService를 때리려고 하면 오류가 발생한다.

BookService가 BookRepository를 의존하고 있기 때문이다.

BookService를 메모리에 띄워줘야한다.

 

이때 @InjectMocks 를 사용한다.

 

@ExtendWith(MockitoExtension.class) // 스프링 컨테이너 내부에 있는것처럼 속이기!
public class BookServiceTest {

    @InjectMocks
    private BookService bookService;

    @Mock
    private BookRepository bookRepository;
    
    @Test
    public void 책등록하기_테스트() {
        // given
        BookSaveReqDto reqDto = new BookSaveReqDto();
        reqDto.setTitle("스프링1강");
        reqDto.setAuthor("메타코딩");

        // stub (행동 정의 = 가설)
        Mockito.when(bookRepository.save(reqDto.toEntity())).thenReturn(new Book(1L, "스프링1강", "메타코딩"));

        // when
        BookRespDto respDto = bookService.책등록하기(reqDto); // 실제 메서드 호출하기

        // then
        assertEquals(1L, respDto.getId());
        assertEquals("스프링1강", respDto.getTitle());
        assertEquals("메타코딩", respDto.getAuthor());
    }
}

 

2. mockito 영역에 BookRepository가 떠있는지 확인하고

있으면 BookService에 의존성을 주입해준다.

 

Mockito로 띄운 애들중에 알맞은 것을 찾아 의존성을 주입해주는게 InjectMocks이다.

서비스레이어에서 !!

 

3. Book이 3번 new 되면 값은 같아도 메모리에 뜬 공간의 주소가 다르다.

그래서 얘들을 내부적으로 비교했을 때 다른 인스턴스로 나온다.

메모리 주소가 다르니까.

 

그래서 Book에다가 @EqualsAndHashCode를 붙여주면 주소를 비교하지 않고 값만 비교한다.

 

@EqualsAndHashCode // 주소비교 안함, 값만 비교함
@Getter
@NoArgsConstructor
@Entity
public class Book {

    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long id;
    private String title;
    private String author;

    @Builder
    public Book(Long id, String title, String author) {
        this.id = id;
        this.title = title;
        this.author = author;
    }
}

 

stub 정의할 때 new한 Book이랑

실제 서비스단에서 save할 때 stub이네? 하고 new 해주는 Book을 비교했는데

메모리 주소가 달랐기 때문에 오류가 난것이다.

 

근데 이렇게 하는거보다 주소까지 굳이 비교하지 말라고 침묵 어노테이션을 걸어주는게 더 나을것같다.

 

당연히 stub으로 정의한 값이 리턴 되어서 then 결과가 true일 수 밖에 없지 않냐 생각할 수 있지만

서비스로직이 짧아서 그렇지 사실 더 많은 로직이 들어가면 당연한 그 값이 안나올 수도 있다.

 

 

[출처]

 

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

 
반응형