Loading...

Spring / / 2022. 5. 30. 12:49

JUnit5 Service 테스트

반응형

서비스가 레파지토리에 의존적이라 레파지토리까지 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

 
반응형