스프링 통합 테스트
repository는 지금 DB까지 연결이 된다. 그러면 테스트도 DB까지 다 연결해서 동작하는 통합 테스트를 살펴보자.
우리가 이전에 했던 테스트들은 전혀 Spring과 관련이 없는 테스트이다. 즉, 순수 자바로 테스트코드를 작성하였다.
우리는 현재 순수한 자바코드로 테스트를 돌릴 수 없다. 왜냐면 데이터베이스 정보를 스프링이 가지고 있기 때문이다.
그래서 이제부터는 테스트를 스프링이랑 엮어서 테스트 해보자.
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
void 회원가입() {
//given
Member member = new Member();
member.setName("spring");
//when
Long savedId = memberService.join(member);
//then
Member findMember = memberService.findOne(savedId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
public void 중복_회원_예외(){
//given
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
//when
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// try {
// memberService.join(member2);
// fail(); //중복 회원이여서 예외가 터져함에도 내려오면 오류이기 때문에 fail
// } catch(IllegalStateException e){ //예외가 정상적으로 발생함.
// assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
// }
//then
}
}
@SpringBootTest라고 하면 SpringBoot에서 Test를 할 수 있게 된다.
@Transactional 이 두가지를 적어주어야 Test에서 Spring을 사용할 수 있다.
객체를 생성해서 넣어주었다. beforeEach()를 지워주자 이게 아니라 이제는 SpringContainer한테 MemberService, MemberRepository를 내놓으라고 해야 한다.
테스트는 사실 제일 끝단에서 만드는 것이기 때문에 제일 편한 방법을 사용하면 된다.
왜냐면 DB에 데이터가 남아있기 때문에 코드에서도 Spring이라고 해서 이미 존재하는 외원입니다. 로 에러가 띄어지게 된다.
h2 데이터에 이미 있기 때문에 에러가 있는 것이므로 h2 에서 sql을 delete from member 를 해주자. 그 후에 select * from member를 해주면 아무것도 없다.
이 상태에서 테스트를 돌려야 한다. 실제 db에 붙는 거기 때문에 보통 테스트 전용 db를 따로 구축을 해서 운여을 한다. 아니면 자기의 local pc에 있는 곳에서 운영을 하는 것임
회원 가입만 돌렸을 경우 정상적으로 잘 출력되는 모습이다.
지금 테스트는 정상적으로 잘되고 있지만 문제점이 하나 있다. 바로 테스트는 몇번이고 계속 반복할 수 있어야 된다는 점이다.
이러한 문제를 해결하기 위해 Transaction을 사용할 것이다. @Transaction -> 데이터베이스는 기본적으로 transaction이라는 것이 있다. insert를 한 다음에 사실 commit이라는 것을 해줘야 데이터베이스에 반영이 된다.
기본적으로 Transaction이라는 commit이 있어서 다 auto insert call을 한 다음에 commit을 하기전에는 데이터베이스에 반영이 안된다.
테스트를 한 다음에 rollback을 해버리면 db에서 데이터가 반영이 안된다.
Test할 때 @Transactional이라고 annotation을 달면 test를 달 때 test를 실행할 때 transaction을 실행하고 db의 데이터를 inset query를 하고 다 넣은 다음에 test가 끝나면 rollback을 해준다. 그래서 db에 넣었던 데이터 깔끔하게 다 지워진다.
test 시작하기전에 transaction을 걸고 query를 다날린다. findOne, 여러 쿼리들이 다 실행된다. 그 다음에 테스트가 끝나면 그 데이터를 rollback한다. 그 다음에 테스트가 끝나면 그 데이터를 rollback한다. 아예 반영을 안한다. 실제 데이터에 반영하지 않음.
- @ SpringBootTest를 하면 Spring과 Test를 함께 실행을 한다. 진짜 스프링을 띄어서 테스트하는 것이다.
- @Transactional 테스트 케이스에 어노테이션이 있으면, 시작 전에는 트랜잭션을 시작하고, 테스트 완료후 항상 롤백한다. 이렇게 하면 DB데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
- 트랙잭션 실행하고 테스트하나 끝나면 롤백한다. 테스트 메서드 하나마다 이렇게 반복한다.
순수하게 자바 코드로만 작성하면서 작은 단위로 테스트를 하는 것을 단위 테스트라고 말한다. → 가급적이면 순수한 단위테스트가 훨씬 더 좋은 테스트일 확률이 높음
Spring container없이 훈련할 수 있는 테스트를 공부하자~! 진짜 좋은 테스트는 이런 단위 테스트를 만드는 것이다.