본문 바로가기
Programming/Java, Spring

spring mock mvc에서 한 번쯤은 만나는 unnecessary stubbing을 lenient 옵션으로 해결하기

by Renechoi 2023. 12. 13.

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);
    }
}
반응형