[SpringBoot] 테스트 환경 구축(MySQL docker)
2024. 3. 22. 10:27ㆍSpring

설명
로컬 MySQL DB를 이용한 테스트환경 구축을 정리했습니다.
Docker 구성
1. Pull image
docker pull --platform linux/amd64 mysql:8.0.28
- 기술적으로는 문제(ARM에서 MySQL 실행)가 해결되지는 않지만 당분간은 platform을 amd로 사용해야한다.
2. Docker run
docker volume create mysql_data
docker run --name mysql -itd \
-p 13306:3306 \
-e MYSQL_ROOT_PASSWORD=password \
-v mysql_data:/var/lib/mysql \
--restart unless-stopped \
mysql:8.0.28
- m1의 경우 해당 옵션 추가 --platform linux/x86_64
- 버전은 운영환경의 DB와 동일하게 설정
- docker volume 설정을 통해 데이터를 영속적으로 저장
3. DDL 추출
- 테스트환경 테이블 구성을 위함.
sudo mysqldump -h [호스트명] -u[유저명] -p[패스워드] -P [포트번호] --all-databases --no-data > ~/db_ddl.sql
4. DDL dump 적용
sudo mysql --host=127.0.0.1 -P 13306 -u root -p < ~/db_ddl.sql
- Amazon Aurora 사용 시에는 AWS AREPLICA_HOST_STATUS 에러가 발생할 수 있으나 Amazon Aurora 관련 테이블 임으로 무시.
5. 테이블 생성 확인
테스트 환경 구축
1. test.resource.application.yml 추가
spring:
application:
name: test
datasource:
url: jdbc:mysql://localhost:13306
username: root
password: catsstudent123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: none
show-sql: true
2. test.resource.application-test.yml 추가
- 해당 yml은 @SpringBootTest + @ActiveProfiles("test") 형태로 사용하기 위함.
- datasource는 로컬 DB 처리
- SpringBootTest 사용 시 개발환경은 master, slave 구성이 되어있기 때문에 DataSource 관련 내용을 전부 모킹처리를 해야하는데 불편함을 제거하기 위해 local MySQL을 보도록 수정
# JWT Secret Key
security:
jwt:
base64-secret: test!@#@!#!@#@!#@!#
test:
cloud-front-url: localhost
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:13306/test
username: root
password: password
read-only: false
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:13306/test
username: root
password: password
read-only: true
3. 유저 로그인 처리
WithCustomMockUser 인터페이스 구성
package com.test.mock.security;
import org.springframework.security.test.context.support.WithSecurityContext;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithCustomMockUser {
}
WithMockCustomUserSecurityContextFactory 구성
- AuthenticationProvider 구현체의 authenticate 매서드를 참고하여 로그인 처리
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {
@Override
public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
SecurityContext contxt = SecurityContextHolder.createEmptyContext();
// AuthenticationProvider 구현체의 authenticate 매서드를 참고하여 로그인 처리
return contxt;
}
}
JPA 테스트 코드 예시
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import({PersistenceConfig.class, QueryDslConfig.class})
class NoteRepositoryTest {
@Autowired
NoteRepository NoteRepository;
private List<Note> createMyNote(Long max_student_id) {
List<Note> noteList = new ArrayList<>();
LongStream.rangeClosed(1L, max_student_id)
.forEach(i -> {
String myNoteId = StringUtil.getUUID();
System.out.println(i);
System.out.println(myNoteId);
Note Note = Note.builder()
.myNoteId(myNoteId)
.studentId(i)
.build();
noteList.add(Note);
Note.save(Note);
});
return noteList;
}
@Test
@DisplayName("노트 목록조회(in student_id)")
void findByNoteIdInAndStudentId() {
// Note 생성
// studentId 1~3학생
List<Note> myNotes1 = createMyNote(3L);
// Note 생성
// studentId 1 학생
List<Note> myNotes2 = createMyNote(1L);
// 테스트 대상 회원
Long testStudent = myNotes2.get(0).getStudentId();
// 대상 회원의 Note id 목록
List<String> testMyNoteIds = Stream.of(myNotes1, myNotes2)
.flatMap(Collection::stream)
.filter(note -> note.getStudentId().equals(testStudent))
.map(Note::getMyNoteId)
.collect(Collectors.toList());
// 대상 회원의 Note 조회
List<Note> myNotesOfTestStudent = NoteRepository.findByMyNoteIdInAndStudentId(testMyNoteIds, testStudent);
// 전체 조회
List<Note> myNotes = mrMyNoteRepository.findAll();
assertThat(myNotes.size()).isEqualTo(4);
assertThat(myNotesOfTestStudent.size()).isEqualTo(2);
}
}
- @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
- 기본적으로 embedded H2 로 설정되어 있기 때문에 local MySQL로 설정하기 위해 Replace None 처리
- @Import({PersistenceConfig.class, QueryDslConfig.class})
- PersistenceConfig
- @EnableJpaAuditing(auditorAwareRef="auditorProvider")
- AuditorProvider bean 등록
- AuditorAware 구현체
- QueryDslConfig
- EntityManager를 @PeristenceContext로 등록
- EntityManager를 Proxy로 감싸서 Thread-Safe를 보장
- JPAQueryFactory Bean 등록
- EntityManager를 @PeristenceContext로 등록
- PersistenceConfig
Controller 테스트 예시
@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
class NoteControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private NoteService noteService;
@Test
@WithCustomMockUser
void getNote() throws Exception {
// getNote 응답값 Mocking
String noteId = "1";
noteResponse noteResponse = NoteResponse.builder()
.myNoteId(myNoteId)
.build();
given(noteService.getNote(noteId))
.willReturn(noteResponse);
// MockUser Controller 테스트
String token = "accessToken";
mockMvc.perform(MockMvcRequestBuilders.get("/v1/my/note/1/")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.noteId").value(noteResponse.getNoteId()));
}
}
- @SpringBootTest
- 애플리케이션의 모든 Bean을 다 등록.
- 속도 이슈 때문에 @WebMvcTest를 사용해야하지만 @Import 시 import한 클래스에 @Value 가 동작하지 않는 이슈 때문에 SpringBootTest 사용.
- 위와 같은 이슈가 없다면 WebMvcTest를 사용하고 Profile 설정을 제거해도 됨
- @ActiveProfiles("test")
- applicaion-test.yml 파일을 읽도록 설정
- applicaion.yml 이 먼저 읽히고 다음 applicaion-test.yml을 읽는다.(Property가 중복이 되면 안됨)
- applicaion-test.yml 파일을 읽도록 설정
- @AutoConfigureMockMvc
- MockMvc 설정
- @WithCustomMockUser
- 유저 로그인 처리
- 자세한 설명은: https://hunstory.tistory.com/85
'Spring' 카테고리의 다른 글
[IntelliJ] 사용중인 Port 프로세스 종료 (0) | 2024.06.19 |
---|---|
[SpringBoot] 테스트 환경 구축 - MockUser (0) | 2024.04.09 |
FeignClient를 이용한 API 호출(HttpClient) (0) | 2024.03.13 |
Spring Security 의인증 관리 (0) | 2024.03.13 |
Spring AOP 정리 (0) | 2024.03.12 |