MVC 단위 테스트를 하면서 발생한 "Unnecessary stubbing" 에러를 lenient
옵션을 통해 해결한 경험을 소개한다.
아래 코드는 실제 코드를 조금 각색하고 추상한 코드이다.
에러가 난 상황
에러 메시지
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at kr.co.....TTLHelperTest.setUp(TTLHelperTest.java:32)
2. -> at kr.co.....TTLHelperTest.setUp(TTLHelperTest.java:34)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
문제의 코드
@ExtendWith(MockitoExtension.class)
class DataPersistenceHelperTest {
@Mock
private DatabaseConfig databaseConfig;
@InjectMocks
private DataPersistenceHelper dataPersistenceHelper;
@BeforeEach
public void setUp() {
lenient().when(databaseConfig.getExtendedLifeSpan()).thenReturn(30);
lenient().when(databaseConfig.getShortLifeSpan()).thenReturn(7);
lenient().when(databaseConfig.getDefaultLifeSpan()).thenReturn(7);
}
@Test
@DisplayName("calculateDuration 메서드가 DataOperationType에 따라 적절한 Duration을 반환하는지 검증한다")
void testCalculateDuration_ValidDataOperationType() {
// Given
DataOperationType dataOperationType = DataOperationType.StoreData;
// When
Duration result = dataPersistenceHelper.calculateDuration(dataOperationType);
// Then
assertEquals(Duration.ofDays(7), result);
}
@Test
@DisplayName("calculateDuration 메서드가 null DataOperationType을 받았을 때 기본 설정 값을 반환하는지 검증한다")
void testCalculateDuration_NullDataOperationType() {
// When
Duration result = dataPersistenceHelper.calculateDuration(null);
// Then
assertEquals(Duration.ofDays(7), result);
}
@Test
@DisplayName("calculateDuration 메서드가 StoreData가 아닌 DataOperationType을 받았을 때 'ExtendedLifeSpan' 값을 반환하는지 검증한다")
void testCalculateDuration_NotStoreDataOperationType() {
// Given
DataOperationType dataOperationType = DataOperationType.RetrieveData;
// When
Duration result = dataPersistenceHelper.calculateDuration(dataOperationType);
// Then
assertEquals(Duration.ofDays(30), result);
}
}
사용된 프로덕션 코드
@Configuration
public class ApplicationConfig extends AbstractApplicationConfiguration {
public final static String COLLECTION_PRIMARY_DATA = "primary-data";
public final static String COLLECTION_SECONDARY_DATA = "secondary-data";
public final static String COLLECTION_TERTIARY_DATA = "tertiary-data";
public final static String COLLECTION_QUATERNARY_DATA = "quaternary-data";
public final static String SCOPE = "appScope";
private Gson gson;
@Value("${application.host}")
private String host;
@Value("${application.userName}")
private String userName;
@Value("${application.password}")
private String password;
@Value("${application.bucketName}")
private String bucketName;
@Getter
@Value("${application.ttl.longTermDays}")
private Integer ttlLongTermDays;
@Getter
@Value("${application.ttl.shortTermDays}")
private Integer ttlShortTermDays;
/**
* {@link ApplicationTTLHelper}의 NullPointerException을 처리하기 위해 추가된 값
*/
@Getter
@Value("${application.ttl.defaultDays}")
private Integer ttlDefaultDays;
public ApplicationConfig(Gson gson) {
this.gson = gson;
}
}
lenient 옵션을 사용한 해결
"Unnecessary stubbing"은 단위 테스트에서 모의 객체(Mock Object)에 대한 설정(Stubbing)이 실제로 테스트 과정에서 호출되지 않았을 때 발생하는 경고이다. 즉, setUp
메서드에서 설정한 stubbing이 실제 테스트에서 사용되지 않는다면 이런 경고가 발생할 수 있다.
이 경우, setUp
메서드에서 Mockito의 lenient
옵션을 사용하여 필요하지 않은 stubbing에 대한 경고를 방지하고, 테스트 케이스의 통과를 돕는다. 예를 들어, 'getExtendedLifeSpan()' 메서드는 특정 테스트에서만 사용되지만, 'getShortLifeSpan()'와 'getDefaultLifeSpan()'는 다른 테스트에서도 사용될 수 있다. 이러한 경우에 lenient를 사용하여 불필요한 경고를 방지한다.
변경된 코드
@ExtendWith(MockitoExtension.class)
class DataCacheHelperTest {
@Mock
private ConfigurationService configService;
@InjectMocks
private DataCacheHelper dataCacheHelper;
/**
* setUp 메서드에서 lenient를 사용한 이유는 다음과 같습니다.
*
* 각각의 테스트마다 설정 값을 가져오는 메서드의 stubbing이 반드시 필요하지 않은 경우가 있습니다.
* lenient를 사용하여 테스트 케이스마다 필요한 stubbing을 "bypass" 함으로써
* "Unnecessary stubbing" 경고를 방지할 수 있습니다.
*
* 예를 들어, 'getLongTermCacheDuration()' 메서드는 'testGetDataDuration_SpecificCondition'
* 테스트에서만 사용되는 반면, 'getShortTermCacheDuration()'와 'getDefaultCacheDuration()'는 다른 테스트에서도 사용됩니다.
* 이와 같이 각 테스트마다 사용되는 stubbing이 중첩되면서도 각기 다른 환경에서 lenient를 사용하여
* 불필요한 경고를 방지하고 테스트 케이스를 통과할 수 있습니다.
*/
@BeforeEach
public void setUp() {
lenient().when(configService.getLongTermCacheDuration()).thenReturn(30);
lenient().when(configService.getShortTermCacheDuration()).thenReturn(7);
lenient().when(configService.getDefaultCacheDuration()).thenReturn(7);
}
@Test
@DisplayName("getDataDuration 메서드가 DataCategory에 따라 적절한 Duration을 반환하는지 검증한다")
void testGetDataDuration_ValidDataCategory() {
// Given
DataCategory dataCategory = DataCategory.GeneralData;
// When
Duration result = dataCacheHelper.getDataDuration(dataCategory);
// Then
assertEquals(Duration.ofDays(7), result);
}
@Test
@DisplayName("getDataDuration 메서드가 null DataCategory을 받았을 때 기본 설정 값을 반환하는지 검증한다")
void testGetDataDuration_NullDataCategory() {
// When
Duration result = dataCacheHelper.getDataDuration(null);
// Then
assertEquals(Duration.ofDays(7), result);
}
@Test
@DisplayName("getDataDuration 메서드가 GeneralData가 아닌 DataCategory를 받았을 때 'LongTermCacheDuration' 값을 반환하는지 검증한다")
void testGetDataDuration_SpecificCondition() {
// Given
DataCategory dataCategory = DataCategory.SpecialData;
// When
Duration result = dataCacheHelper.getDataDuration(dataCategory);
// Then
assertEquals(Duration.ofDays(30), result);
}
}
반응형
'Programming > Java, Spring' 카테고리의 다른 글
Webflux는 얼마나 빠를까? Spring Mvc Vs. Webflux 성능 비교 테스트 (0) | 2023.12.14 |
---|---|
spring 단위 테스트에서 autowired와 mockbean의 차이 (feat. spybean) (0) | 2023.12.13 |
스프링 캐싱 메커니즘에서 @Cacheable과 @Cacheput 차이점은 ? (0) | 2023.12.13 |
Spring Webflux - 배경, 개념 Cpu bound vs I/O Bound, block vs non-block, mvc vs webflux (1) | 2023.10.16 |
스프링 웹 환경에서 요청 응답 플로우 (request, filter, interceptor, controller, exceptionHandler) (0) | 2023.10.16 |