사전 준비
- 서브 도메인 브랜치 생성 후 Entity 생성
- Entity와 대응하는 Repository 생성
테스트 환경만의 application-test.yml
spring:
config:
activate:
on-profile: test # 테스트 클래스에서 설정 파일을 구분하기 위한 프로필명을 설정
datasource:
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop # 매 실행마다 데이터베이스를 삭제하고 새로 생성하는 설정
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
globally_quoted_identifiers: true
show_sql: true
generate-ddl: true
database-platform: org.hibernate.dialect.MySQL8Dialect
database: h2 # h2 데이터베이스 지정
redis: # redis 설정
host: 127.0.0.1
port: 6379
password: auth.!
h2:
console:
enabled: true
jwt:
secret: 999d95876cb0e556f8546fbee8356a3e919cf761b863c5efd39b1672cef5b6d0c7a40a29b176169aa1ab705f38fa1cf7979f9815fef6d32c2e30be9d980f5d41
expired:
access_token: 3
refresh_token: 5
@TestPropertySource란?
테스트 케이스에서 원하는 시나리오를 설정하기 위해 특정 설정을 사용해야 하는 경우가 있습니다. 이러한 상황에서 @TestPropertySource 어노테이션을 이용해 프로젝트에서 사용되는 다른 설정 파일보다 우선하여 적용할 수 있습니다.
1. @TestPropertySource 사용 시 주의점
하지만 yml 형식의 설정 파일인 경우, 테스트를 실행하게 되면 직접 지정한 "application-test.yml" 이 아니라 기본 값인 "application.yml" 파일을 참조하게 되어 에러가 발생할 수도 있습니다.
위 에러는 개발 환경에서 사용하는 Mysql에 Auto_Increment 값 초기화하는 SQL을 H2 문법으로 실행시켜서 발생했습니다.
이를 해결하기 위해서는 "application-test.yml" 에서 지정한 "on-profile" 값을 테스트 클래스에 @ActiveProfile 어노테이션을 통해 프로필을 지정해주면 application-test.yml를 적용할 수 있습니다.
2. @AutoConfigureTestDatabase
테스트 환경에서는 H2를 MySQL 모드로 사용할 수 있게 기본 H2 In-memory가 아닌 H2의 Mode와 hibernate의 Dialect를 MYSQL 설정하여 사용하려고 했습니다. 그래서 application-test.yml에 각종 설정을 했지만, 적용이 되지 않고 자동으로 구성된 H2 In-memory를 사용하는 문제점이 발생했습니다.
기본적으로 내장되어 있는 DataSource와 Connection를 구현체로 테스트를 진행하기 때문에 생기는 문제였습니다.
이 문제점은 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 이 설정을 사용하면 해결할 수 있습니다. 위 설정은 자동으로 된 설정을 replace 해서 해당 설정이 동작하지 않고, 내가 설정한 파일대로 만들어진 DataSource가 Bean으로 등록되게끔 합니다.
3. 테스트 코드
@DataJpaTest
@ActiveProfiles("test")
@TestPropertySource(locations = "classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MemberRepositoryTest {
@BeforeEach
void registerMember() {
this.entityManager.createNativeQuery("ALTER TABLE member ALTER member_id RESTART WITH 1").executeUpdate();
Member member = Member.builder()
.email(EMAIL)
.password(PASSWORD)
.build();
memberRepository.save(member);
}
@DisplayName("Member 생성 테스트")
@Test
void memberRegisterTest() {
assertTrue(true);
}
}
H2 기반 in-memory 테스트 환경 구축
1. 의존성 설정
dependencies {
testImplementation 'com.h2database:h2'
}
2. application-test.yml
...
datasource:
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
format_sql: true
globally_quoted_identifiers: true
show_sql: true
generate-ddl: true
database-platform: org.hibernate.dialect.MySQL8Dialect
database: h2
h2:
console:
enabled: true
...
@DataJpaTest란?
JPA에 관련된 요소들만 테스트하기 위한 어노테이션으로 JPA 테스트에 관련된 설정들만 적용해주는 특징이 있습니다.
@DataJpaTest 어노테이션은 내부적으로 @Transactional을 포함하고 있기 때문에 데이터베이스 롤백을 위해 테스트 메소드 마다 별도로 @Transactional 어노테이션을 붙힐 필요가 없습니다.
@DataJpaTest
@ActiveProfiles("test")
@TestPropertySource(locations = "classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MemberRepositoryTest {
@BeforeEach
void registerMember() {
this.entityManager.createNativeQuery("ALTER TABLE member ALTER member_id RESTART WITH 1").executeUpdate();
Member member = Member.builder()
.email(EMAIL)
.password(PASSWORD)
.build();
memberRepository.save(member);
}
@DisplayName("Member 생성 테스트")
@Test
// @Transactional 없이도 자동으로 데이터베이스 롤백을 수행합니다.
void memberRegisterTest() {
assertTrue(true);
}
}
단위 테스트 케이스
1. @BeforeEach
void registerMember() {
this.entityManager.createNativeQuery("ALTER TABLE member ALTER member_id RESTART WITH 1").executeUpdate();
Member member = Member.builder()
.email(EMAIL)
.password(PASSWORD)
.build();
memberRepository.save(member);
}
데이터베이스에서 PK값이 Auto_Increment로 자동 생성되기 때문에 테스트 시작 전 1로 초기화하는 로직과 회원을 등록하는 로직을 수행하는 메소드입니다.
2. 데이터 삽입
@DisplayName("Member 생성 테스트")
@Test
void memberRegisterTest() {
assertTrue(true);
}
데이터 삽입은 @BeforeEach에서 데이터 삽입(회원 등록)이 발생하기 때문에 예외가 발생하는지만 확인하기 위해서 위와 같이 코드를 작성했습니다.
3. 데이터 조회
@DisplayName("Member 조회 테스트")
@Test
void memberFindTest() {
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
Member member = op.get();
assertEquals(member.getMemberId(), MEMBER_ID);
assertEquals(member.getEmail(), EMAIL);
assertEquals(member.getPassword(), PASSWORD);
}
데이터 조회는 @BeforeEach에서 삽입한 데이터를 가져와서 값이 일치하는지 확인하는 과정을 통해 테스트 케이스를 진행합니다.
4. 데이터 수정
@DisplayName("Member 수정 테스트")
@Test
void memberModifyTest() {
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
String modifyEmail = "modify@test.com";
String modifyPassword = "5678";
Member member = op.get();
member.setEmail(modifyEmail);
member.setPassword(modifyPassword);
memberRepository.flush();
op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
member = op.get();
assertEquals(modifyEmail, member.getEmail());
assertEquals(modifyPassword, member.getPassword());
}
데이터 수정은 @BeforeEach에서 삽입한 데이터를 가져와서 수정하고, flush()로 데이터베이스로 플러싱을 진행합니다. 이후 다시 데이터베이스에서 데이터를 조회하고 수정한 값과 일치하는지 여부를 판별합니다.
5. 데이터 삭제
@DisplayName("Member 삭제 테스트")
@Test
void memberDeleteTest() {
memberRepository.deleteById(MEMBER_ID);
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertFalse(op.isPresent());
}
데이터 삭제는 @BeforeEach에서 삽입한 데이터를 삭제하고, 다시 조회했을 때 데이터 유무를 판단하는 과정으로 테스트 케이스가 진행됩니다.
전체 코드
@DataJpaTest
@ActiveProfiles("test")
@TestPropertySource(locations = "classpath:application-test.yml")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MemberRepositoryTest {
@Autowired
private EntityManager entityManager;
@Autowired
private MemberRepository memberRepository;
private final String EMAIL = "test@test.com";
private final String PASSWORD = "1234";
private final Long MEMBER_ID = 1L;
@BeforeEach
void registerMember() {
this.entityManager.createNativeQuery("ALTER TABLE member ALTER member_id RESTART WITH 1").executeUpdate();
Member member = Member.builder()
.email(EMAIL)
.password(PASSWORD)
.build();
memberRepository.save(member);
}
@DisplayName("Member 생성 테스트")
@Test
void memberRegisterTest() {
assertTrue(true);
}
@DisplayName("Member 조회 테스트")
@Test
void memberFindTest() {
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
Member member = op.get();
assertEquals(member.getMemberId(), MEMBER_ID);
assertEquals(member.getEmail(), EMAIL);
assertEquals(member.getPassword(), PASSWORD);
}
@DisplayName("Member 수정 테스트")
@Test
void memberModifyTest() {
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
String modifyEmail = "modify@test.com";
String modifyPassword = "5678";
Member member = op.get();
member.setEmail(modifyEmail);
member.setPassword(modifyPassword);
memberRepository.flush();
op = memberRepository.findById(MEMBER_ID);
assertTrue(op.isPresent());
member = op.get();
assertEquals(modifyEmail, member.getEmail());
assertEquals(modifyPassword, member.getPassword());
}
@DisplayName("Member 삭제 테스트")
@Test
void memberDeleteTest() {
memberRepository.deleteById(MEMBER_ID);
Optional<Member> op = memberRepository.findById(MEMBER_ID);
assertFalse(op.isPresent());
}
}
'Java > Junit' 카테고리의 다른 글
[Spring] JUnit5 기반 기능 구현해보기 - 구조 설계 (0) | 2023.08.30 |
---|---|
[Spring] JUnit5 Assertions & Assumptions (0) | 2023.08.30 |
[Spring] JUnit5으로 테스트해보기 (0) | 2023.08.30 |
[Spring] Junit이란? (0) | 2023.08.28 |