문제 상황 : Test Method끼리 공유하는 Bean 필요
JUnit5를 이용해 Spring Boot 프로젝트의 테스트코드를 작성하는 도중이었다. 하나의 @SpringBootTest 클래스 안에 존재하는 여러 개의 @Test method가 공유하는 Repository를 생성하고 싶었다.
코드로 말하면 아래와 같은 상황이다. @BeforeAll이 붙은 method가 테스트 method들을 실행하기 전 10명의 유저를 userRepository에 저장한다. 그 후 2개의 test method가 userRepository에 저장된 것을 테스트하는 것이다.
@SpringBootTest
public class EntityTest {
@Autowired
private UserRepository userRepository;
@BeforeAll
public void setDB() {
for (int i = 1; i <= 10; i++) {
User user = User.builder().name("유저 "+i).build();
userRepository.save(user);
}
}
@Test
public void testUserNum() {
assertEquals(10, userRepository.findAll().size());
}
@Test
public void testUserType() {
for (User user : userRepository.findAll()) {
assertInstanceOf(User.class, user);
}
}
위 Test Class를 돌리면 에러가 뜬다.
에러 이유 : Repository는 static이 될 수 없다
로그를 살펴보자.

@BeforeAll 어노테이션이 붙은 method는 static이어야 한다고? @BeforeAll 문서에도 같은 내용이 있다.

JUnit5에서, 테스트 클래스의 life cycle은 기본적으로 PER_METHOD로 설정되어 있다. 테스트 클래스 내부의 테스트 method들이 실행될 때마다 테스트 클래스가 새로 생성되고 삭제되는 것이다. 때문에 static으로 선언되지 않는 한 테스트 method들이 공유하는 클래스 내부 요소들을 관리할 수는 없다.
생명주기가 PER_METHOD인 상황에서 @BeforeXX, @AfterXX가 붙은 method들의 실행 시점이 궁금하다면 더보기를 참고하자.
Lifecycle.PER_METHOD 모드에서 Constructor, @BeforeAll, @BeforeEach, @AfterAll, @AfterEach가 실행되는 시점을 관찰하기 위해 Test Class 하나를 만들었다. 각각 method가 실행될 때마다 로그를 남길 수 있도록 했다.
@SpringBootTest
public class LifeCycleTest {
LifeCycleTest() {
System.out.println("construct");
}
@BeforeAll
public static void beforeTest() {
System.out.println("@BeforeAll");
}
@BeforeEach
public void beforeEach() {
System.out.println("@BeforeEach");
}
@AfterAll
public static void afterAll() {
System.out.println("@AfterAll");
}
@AfterEach
public void afterEach() {
System.out.println("@AfterEach");
}
@Test
public void firstTest() {
System.out.println("first test");
}
@Test
public void secondTest() {
System.out.println("second test");
}
}
위 테스트 클래스를 실행해보면, Spring boot test에서 @BeforeAll 로그가 찍히는 것은 클래스가 생성되기도 전이다. static으로 선언된 @BeforeAll method는 클래스 생성 이전에 실행되는 메쏘드인 것이다!

나머지 ~~Each method들은 각각의 테스트 method가 실행되기 전후에, @AfterAll 역시 모든 method가 끝나고 한번만 잘 출력되고 있다. 특별한 점은 secondTest()가 실행되기 전 construct 로그가 한 번 더 찍힌다는 것이다.

테스트 클래스의 생명 주기가 PER_METHOD이므로, 새로운 test method가 실행되기 전 테스트 클래스의 인스턴스가 새로 만들어졌다고 짐작할 수 있다.
그런데 JPA repository는 Interface다. Spring Boot 프로젝트에서 필요한 모든 Bean들이 생성되고, Spring Data JPA와 Hibernate가 repository implement를 마치고, Bean들이 Spring context에 올라가고 난 뒤에야 사용이 가능한 것이다. 때문에 프로그램 시작과 동시에 메모리에 적재되는 static method 안에서 이를 관리할 수는 없다.
해결 방법 : @TestInstance(TestInstance.LifeCycle.PER_CLASS)
그렇다면 테스트 클래스의 life cycle을 바꿔야한다. EntityTest 클래스에 @TestInstance(TestInstance.LifeCycle.PER_CLASS)를 붙여 테스트 클래스의 생명주기를 클래스 단위로 바꿔주자.
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EntityTest {
@Autowired
private UserRepository userRepository;
@BeforeAll
public void setDB() {
for (int i = 1; i <= 10; i++) {
User user = User.builder().name("유저 "+i).build();
userRepository.save(user);
}
}
@Test
public void testUserNum() {
assertEquals(10, userRepository.findAll().size());
}
@Test
public void testUserType() {
for (User user : userRepository.findAll()) {
assertInstanceOf(User.class, user);
}
}
그럼 테스트 method가 몇 개이든 테스트 클래스는 한 번만 생성될 것이니 @BeforeAll, @AfterAll method가 static일 필요는 없게 된다. testUserNum(), testUserType()은 동일한 userRepository를 공유한다. 실제로 위 테스트 클래스는 별 문제 없이 잘 실행된다.
생명주기가 PER_CLASS인 테스트 클래스에서 @BeforeXX, @AfterXX가 붙은 test method가 실행되는 시점이 궁금하다면 더보기를 참고하자.
그렇다면 @TestInstance(TestInstance.LifeCycle.PER_CLASS)를 붙인 테스트 클래스에서 각각의 method들은 언제 실행될까? 위 접은글과 같은 환경에서 생명주기를 바꾸는 어노테이션만 붙이고 실행해보면 출력되는 결과는 아래와 같다.

일단 constructor는 프로그램 시작과 동시에 실행됐다.
생명주기가 PER_METHOD였을 때는 constructor 위에 @BeforAll method가 찍혔지만,

생명주기가 PER_CLASS로 바뀐 지금은 @BeforeAll method가 class 생성 이후에 실행됨을 확인했다. 더불어 construt 로그가 한 번만 찍혔다. @Test method가 여러개더라도 새로운 테스트 클래스가 생성되지 않았다.
생명주기에 따라 테스트 클래스의 각 method들의 호출 시점을 정리해놓은 그림이다.

별개로 굳이 생명주기를 건들지 않고 @BeforeEach, @AfterEach를 사용하여 각 테스트 method마다 DB데이터를 save/delete하는 방법도 있긴 하다.
REFERENCE)
https://www.baeldung.com/java-beforeall-afterall-non-static
문제 상황 : Test Method끼리 공유하는 Bean 필요
JUnit5를 이용해 Spring Boot 프로젝트의 테스트코드를 작성하는 도중이었다. 하나의 @SpringBootTest 클래스 안에 존재하는 여러 개의 @Test method가 공유하는 Repository를 생성하고 싶었다.
코드로 말하면 아래와 같은 상황이다. @BeforeAll이 붙은 method가 테스트 method들을 실행하기 전 10명의 유저를 userRepository에 저장한다. 그 후 2개의 test method가 userRepository에 저장된 것을 테스트하는 것이다.
@SpringBootTest
public class EntityTest {
@Autowired
private UserRepository userRepository;
@BeforeAll
public void setDB() {
for (int i = 1; i <= 10; i++) {
User user = User.builder().name("유저 "+i).build();
userRepository.save(user);
}
}
@Test
public void testUserNum() {
assertEquals(10, userRepository.findAll().size());
}
@Test
public void testUserType() {
for (User user : userRepository.findAll()) {
assertInstanceOf(User.class, user);
}
}
위 Test Class를 돌리면 에러가 뜬다.
에러 이유 : Repository는 static이 될 수 없다
로그를 살펴보자.

@BeforeAll 어노테이션이 붙은 method는 static이어야 한다고? @BeforeAll 문서에도 같은 내용이 있다.

JUnit5에서, 테스트 클래스의 life cycle은 기본적으로 PER_METHOD로 설정되어 있다. 테스트 클래스 내부의 테스트 method들이 실행될 때마다 테스트 클래스가 새로 생성되고 삭제되는 것이다. 때문에 static으로 선언되지 않는 한 테스트 method들이 공유하는 클래스 내부 요소들을 관리할 수는 없다.
생명주기가 PER_METHOD인 상황에서 @BeforeXX, @AfterXX가 붙은 method들의 실행 시점이 궁금하다면 더보기를 참고하자.
Lifecycle.PER_METHOD 모드에서 Constructor, @BeforeAll, @BeforeEach, @AfterAll, @AfterEach가 실행되는 시점을 관찰하기 위해 Test Class 하나를 만들었다. 각각 method가 실행될 때마다 로그를 남길 수 있도록 했다.
@SpringBootTest
public class LifeCycleTest {
LifeCycleTest() {
System.out.println("construct");
}
@BeforeAll
public static void beforeTest() {
System.out.println("@BeforeAll");
}
@BeforeEach
public void beforeEach() {
System.out.println("@BeforeEach");
}
@AfterAll
public static void afterAll() {
System.out.println("@AfterAll");
}
@AfterEach
public void afterEach() {
System.out.println("@AfterEach");
}
@Test
public void firstTest() {
System.out.println("first test");
}
@Test
public void secondTest() {
System.out.println("second test");
}
}
위 테스트 클래스를 실행해보면, Spring boot test에서 @BeforeAll 로그가 찍히는 것은 클래스가 생성되기도 전이다. static으로 선언된 @BeforeAll method는 클래스 생성 이전에 실행되는 메쏘드인 것이다!

나머지 ~~Each method들은 각각의 테스트 method가 실행되기 전후에, @AfterAll 역시 모든 method가 끝나고 한번만 잘 출력되고 있다. 특별한 점은 secondTest()가 실행되기 전 construct 로그가 한 번 더 찍힌다는 것이다.

테스트 클래스의 생명 주기가 PER_METHOD이므로, 새로운 test method가 실행되기 전 테스트 클래스의 인스턴스가 새로 만들어졌다고 짐작할 수 있다.
그런데 JPA repository는 Interface다. Spring Boot 프로젝트에서 필요한 모든 Bean들이 생성되고, Spring Data JPA와 Hibernate가 repository implement를 마치고, Bean들이 Spring context에 올라가고 난 뒤에야 사용이 가능한 것이다. 때문에 프로그램 시작과 동시에 메모리에 적재되는 static method 안에서 이를 관리할 수는 없다.
해결 방법 : @TestInstance(TestInstance.LifeCycle.PER_CLASS)
그렇다면 테스트 클래스의 life cycle을 바꿔야한다. EntityTest 클래스에 @TestInstance(TestInstance.LifeCycle.PER_CLASS)를 붙여 테스트 클래스의 생명주기를 클래스 단위로 바꿔주자.
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EntityTest {
@Autowired
private UserRepository userRepository;
@BeforeAll
public void setDB() {
for (int i = 1; i <= 10; i++) {
User user = User.builder().name("유저 "+i).build();
userRepository.save(user);
}
}
@Test
public void testUserNum() {
assertEquals(10, userRepository.findAll().size());
}
@Test
public void testUserType() {
for (User user : userRepository.findAll()) {
assertInstanceOf(User.class, user);
}
}
그럼 테스트 method가 몇 개이든 테스트 클래스는 한 번만 생성될 것이니 @BeforeAll, @AfterAll method가 static일 필요는 없게 된다. testUserNum(), testUserType()은 동일한 userRepository를 공유한다. 실제로 위 테스트 클래스는 별 문제 없이 잘 실행된다.
생명주기가 PER_CLASS인 테스트 클래스에서 @BeforeXX, @AfterXX가 붙은 test method가 실행되는 시점이 궁금하다면 더보기를 참고하자.
그렇다면 @TestInstance(TestInstance.LifeCycle.PER_CLASS)를 붙인 테스트 클래스에서 각각의 method들은 언제 실행될까? 위 접은글과 같은 환경에서 생명주기를 바꾸는 어노테이션만 붙이고 실행해보면 출력되는 결과는 아래와 같다.

일단 constructor는 프로그램 시작과 동시에 실행됐다.
생명주기가 PER_METHOD였을 때는 constructor 위에 @BeforAll method가 찍혔지만,

생명주기가 PER_CLASS로 바뀐 지금은 @BeforeAll method가 class 생성 이후에 실행됨을 확인했다. 더불어 construt 로그가 한 번만 찍혔다. @Test method가 여러개더라도 새로운 테스트 클래스가 생성되지 않았다.
생명주기에 따라 테스트 클래스의 각 method들의 호출 시점을 정리해놓은 그림이다.

별개로 굳이 생명주기를 건들지 않고 @BeforeEach, @AfterEach를 사용하여 각 테스트 method마다 DB데이터를 save/delete하는 방법도 있긴 하다.
REFERENCE)
https://www.baeldung.com/java-beforeall-afterall-non-static