@MybatisTest 클래스 레벨의 @Sql 이 수행되지 않는 경우
문제 상황
- MybatisTest 에서 메서드 레벨에서
@Sql
을 수행한 경우 클래스 레벨의@Sql
이 동작하지 않는 상황 발견 - 테스트 코드의 신뢰성에 문제가 생기는 경우가 발생하였다.
공식문서
문서상 @Sql
에 대해
- 해당 애너테이션은 클래스 레벨과 메서드 레벨에서 동시에 선언하는 경우 기본적으로 메서드 레벨의
@Sql
선언이 클래스 레벨의 선언을 오버라이딩한다. @SqlMergeMode
어노테이션을 통해- 해당 동작을 변경할 수 있음.
이전 상황
@Sql(statements = {"SELECT * FROM ORDERS",
"UPDATE ORDERS SET DELETED_DATE = NOW() WHERE ID = 1"})
@DisplayName("주문 다건 조회(selectOrdersWithPaginationAndNotDeleted) -> 삭제되지 않은 1건 조회 정상 조회")
void When_SelectOrdersWithPaginationAndNotDeleted_Expect_ReturnOrdersWithPaginationAndReturnTwoOrders() {
// given as @Sql
int pageNum = 0;
int pageSize = 10;
OrderPageRequestDTO requestDTO = mock(OrderPageRequestDTO.class);
given(requestDTO.getPageNum()).willReturn(pageNum);
given(requestDTO.getPageSize()).willReturn(pageSize);
// when
List<Order> orders = orderMapper.selectOrdersWithPaginationAndNotDeleted(requestDTO);
// then
assertThat(orders).isNotNull();
assertThat(orders.size()).isEqualTo(1);
}
@Sql
어노테이션이 오버라이딩을 해버리기에 ->@SqlMergeMode
로 변경을 해보았음.
변경 후
@Test
@SqlMergeMode(SqlMergeMode.MergeMode.MERGE)
@Sql(statements = "UPDATE ORDERS SET DELETED_DATE = NOW() WHERE ID = 1")
@DisplayName("주문 다건 조회(selectOrdersWithPaginationAndNotDeleted) -> 삭제되지 않은 1건 조회 정상 조회")
void When_SelectOrdersWithPaginationAndNotDeleted_Expect_ReturnOrdersWithPaginationAndReturnTwoOrders() {
// given as @Sql
int pageNum = 0;
int pageSize = 10;
OrderPageRequestDTO requestDTO = mock(OrderPageRequestDTO.class);
given(requestDTO.getPageNum()).willReturn(pageNum);
given(requestDTO.getPageSize()).willReturn(pageSize);
// when
List<Order> orders = orderMapper.selectOrdersWithPaginationAndNotDeleted(requestDTO);
// then
assertThat(orders).isNotNull();
assertThat(orders.size()).isEqualTo(1);
}
- 테스트 정상 통과!
- 테스트 마다 별도의 조건을 Sql 로 직접 주고 싶은 경우 해당 어노테이션으로 줄 수 있게 되었다.
여기서 잠깐..
- @SqlMergeMode 어노테이션의 원리는 어떤식일까?
-
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface SqlMergeMode { /** * Indicates whether method-level {@code @Sql} annotations should be merged * with class-level {@code @Sql} annotations or override them. */ MergeMode value(); /** * Enumeration of <em>modes</em> that dictate whether method-level {@code @Sql} * declarations are merged with class-level {@code @Sql} declarations. */ enum MergeMode { /** * Indicates that method-level {@code @Sql} declarations should be merged * with class-level {@code @Sql} declarations, with class-level SQL * scripts and statements executed before method-level scripts and * statements. */ MERGE, /** * Indicates that method-level {@code @Sql} declarations should override * class-level {@code @Sql} declarations. */ OVERRIDE } }
- 일단 내부적으로 MergeMode 라는 ENUM 타입의 내부 클래스를 가지고 있다.
- 일단
@Sql
의 경우SqlScriptsTestExecutionListener
에 의하여 테스트 실행 중에 SQL 스크립트를 연결할 수 있으며, - 해당 리스너는 해당 애너테이션을 처리해 지정된 시점에 SQL 스크립트를 실행할 수 있도록 도와준다.
public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListener { private static final Log logger = LogFactory.getLog(SqlScriptsTestExecutionListener.class);
- 머지 코드의 감지
-
/** * Determine if method-level {@code @Sql} annotations should be merged with * class-level {@code @Sql} annotations. */ private boolean mergeSqlAnnotations(TestContext testContext) { SqlMergeMode sqlMergeMode = getSqlMergeModeFor(testContext.getTestMethod()); if (sqlMergeMode == null) { sqlMergeMode = getSqlMergeModeFor(testContext.getTestClass()); } return (sqlMergeMode != null && sqlMergeMode.value() == MergeMode.MERGE); }
- 테스트 메서드에서
sqlMergeMode
어노테이션이 있는지 검증하고 MergeMode
가MERGE
라면true
를 반환한다.- 스크립트 실행 부
-
/** * Execute SQL scripts configured via {@link Sql @Sql} for the supplied * {@link TestContext} and {@link ExecutionPhase}. */ private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) { Method testMethod = testContext.getTestMethod(); Class<?> testClass = testContext.getTestClass(); if (mergeSqlAnnotations(testContext)) { executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false); } else { Set<Sql> methodLevelSqlAnnotations = getSqlAnnotationsFor(testMethod); if (!methodLevelSqlAnnotations.isEmpty()) { executeSqlScripts(methodLevelSqlAnnotations, testContext, executionPhase, false); } else { executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); } } }
- merge 모드라면
-
executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); executeSqlScripts(getSqlAnnotationsFor(testMethod), testContext, executionPhase, false);
- 클래스 레벨에서 한번
- 메서드 레벨에서 한번 스크립트가 수행된다.
- 머지라곤 하지만, 그냥 순차적으로 스크립트 수행이라
머지
의 의미가 맞나? 싶다.- 사실상
추가
의 의미가 더 강한 것 같다.
'자바 > 리팩토링' 카테고리의 다른 글
[이벤트 리스너] @TransactionalEventListener (0) | 2024.02.07 |
---|---|
__mysql 5.7 에서 H2 1.4.200 호환을 위한 SCHEMA 수정__ (0) | 2024.02.07 |
[프로젝트] 모킹 클래스의 private 필드에 테스트 데이터 삽입 방법 - `ReflectionTestUtils` (0) | 2024.02.04 |
[프로젝트] 시큐리티 권한 체크 `@PreAuthorize 또는 @Secured` (0) | 2024.02.02 |
[프로젝트] RestDocs 커스텀 유저 모킹 `@MockMember` 적용기 (0) | 2024.02.02 |