이전 편 보러가기

해당 포스팅에선 JUnit 에서 트랜잭션이 어떤 식으로 동작하는지 알아 볼것이다!

동작 & 트랜잭션

  • 메서드 실행(트랜잭션 시작) -> 종료(트랜잭션 종료) -> RollBack

  • 실제 서버에서는 RunTimeException이 발생할 때 RollBack이 되고, 이슈가 없다면 Commit이 된다.

📌 의문점

BeforeEach 메소드를 실행한 다음에 @Sql("classpath:db/tableInit.sql")을 실행하면 테이블이 삭제되는 거니까 BeforeEach 메소드에서 저장된 데이터는 없어야 한다. 하지만 BeforeEach에서 저장된 데이터가 그대로 남게 됨.

🤷‍♀️ 왜 테이블을 삭제했는데 데이터가 그대로 남아있지❓

트랜잭션이 시작되고, save된 데이터는 HDD(하드디스크)에 저장되는 것이 아니라, 메모리(램)에 저장이 된다. 이때 @Sql("classpath:db/tableInit.sql")을 통해서 테이블을 삭제하고 초기화 하는 작업은 HDD에 저장된 데이터를 삭제해주는 것이지, 메모리(램)에 저장된 데이터를 삭제하는 것이 아니다.

Commit : 메모리(램)에 존재하는 data를 HDD(하드디스크)에 저장하는 것
RollBack : 메모리(램)에 존재하는 data를 HDD에 저장하지 않고, 메모리의 데이터를 삭제하는 것

h2처럼 임의로 만든 db가 아닌 실제 서버에서 검증을 해야 한다면 id 검증 말고 다른 방식으로 검증 해주자.

BookService 본코드 작성

// 2. 책목록보기
    public List<BookRespDto> 책목록보기(){
        return bookRepository.findAll().stream()
                    .map((new BookRespDto() :: toDto)
                    .collect(Collectors.toList)); 
                    // map으로 해주는 이유 : map으로 하면 stream으로 들어온 object를 복제하여 다른 타입으로 변경이 가능하다. 
    }

🧩Mockito - 가짜 환경 만들기

Mockito란?
· Mock 객체를 쉽게 만들고, 관리하고, 검증할 수 있는 방법을 제공하는 프레임워크

애플리케이션에서 데이터베이스, 외부 API 등을 테스트할 때, 해당 제품들이 어떻게 작동하는지 항상 사용하면서 테스트를 작성한다면 매우 불편할 것이다. 이럴 때 어떻게 작동하는지 예측을 하여 Mock 객체를 만들어서 사용하면, 편리한 테스트가 가능하다.

package site.metacoding.junitproject.service;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;

import site.metacoding.junitproject.domain.BookRepository;
import site.metacoding.junitproject.util.MailSender;
import site.metacoding.junitproject.web.dto.BookRespDto;
import site.metacoding.junitproject.web.dto.BookSaveReqDto;

@ExtendWith(MockitoExtension.class)
public class BookServiceTest {

    @InjectMocks
    private BookService bookService;    // 얘는 autowired로 메모리에 못 올린다. 

    @Mock
    private MailSender mailSender;      

    @Mock
    private BookRepository bookRepository;

    @Test
    public void 책등록하기_테스트(){
        // given (파라미터로 들어올 데이터)
        BookSaveReqDto dto = new BookSaveReqDto();
        dto.setTitle("junit강의");
        dto.setAuthor("이보통");
        
        // stub (가설)
        when(bookRepository.save(any())).thenReturn(dto.toEntity());   // 행동정의  - return 값을 정하는 것(thenReturn값으로 리턴값을 대체해준다.)
        when(mailSender.send()).thenReturn(true);                                  // 가설이라고 생각하면 됨

        // when (실행)
        BookRespDto bookRespDto = bookService.책등록하기(dto);
        
        // then (검증)
  	    assertThat(dto.getTitle()).isEqualTo(bookRespDto.getTitle());   // assertThat : 실제값, isEqualTo : 내가 기대하는 값
        assertThat(dto.getAuthor()).isEqualTo(bookRespDto.getAuthor());
        
    }
    
     @Test
    public void 책목록보기_테스트(){
        // given (파라미터로 들어올 데이터)

        // stub (가설)
        List<Book> books = new ArrayList<>();
        books.add(new Book(1L, "junit강의", "메타코딩"));
        books.add(new Book(2L, "spring강의", "겟인데어"));

        when(bookRepository.findAll()).thenReturn(books);   // books가 return되도록 설정

        // when (실행)
        List<BookRespDto> dtos = bookService.책목록보기();

        // then (검증)
        assertThat(dtos.get(0).getTitle()).isEqualTo("junit강의");
    }
}
  • @ExtendWith(MockitoExtension.class) : 가짜 환경을 만들어준다.
  • @Mock : 가짜 환경에 가짜 Repository, 가짜 MailSender가 올라간다.
  • @InjectMocks : 클래스를 new 시켜주고, 가짜 환경에 올라가 있는 Mock클래스들이 BookService에 주입된다.

🔧 Repository에 어떤값을 넣든 가짜 클래스이기 때문에 제대로 된 값이 나올 수가 없다. 따라서 when().thenReturn()을 통해서 내가 원하는 값이 return 되도록 설정을 해준다.

⭐️즉, 행동정의 를 해주는 것이다.⭐️

assertEquals보다는 assertThat이 더 좋은 이유

  • 테스트에서 필요한 거의 모든 메소드를 제공
  • 메소드 체이닝을 지원해서 더 짧고 간결하게 코드 작성 가능
  • 가독성 높음

🚀책목록보기_테스트 실행 결과

실패하였다,,

실패원인 : "junit강의"를 기대했는데 "spring강의"를 return하였다.
본코드에 문제가 있는 것으로 예상됨.

🔨본코드 수정

 // 2. 책목록보기
    public List<BookRespDto> 책목록보기(){
        List<BookRespDto> dtos =  bookRepository.findAll().stream()
                    .map((bookPS) -> new BookRespDto().toDto(bookPS))
                    .collect(Collectors.toList()); // map으로 해주는 이유 : map으로 하면 stream으로 들어온 object를 복제하여 다른 타입으로 변경이 가능하다. 

        return dtos;	
    }

위와 같이 수정해주고 테스트 코드를 다시 실행해주면 테스트 성공한다


👉 AssertJ에 대해 더 알고싶다면

profile
백엔드 공부중입니다!

0개의 댓글

Powered by GraphCDN, the GraphQL CDN