본문 바로가기
IT/Spring

Private Method Test 하기

by 봉즙 2023. 9. 14.

Test 코드를 작성하다보면 private method를 테스트해야할 경우가 생긴다.

이러한 경우 Reflection을 사용하면 테스트가 가능하다.

여기서는 Spring의 ReflectionTestUtils와 Java의 Reflection을 사용하는 방법에 대해 작성하였다.

data class Human(
    val age: Int
) {

    private fun isAdult(): Boolean {
        return age > 19
    }
}

import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.springframework.test.util.ReflectionTestUtils
import java.lang.reflect.Method

class HumanTest {
    @Test
    fun springTest() {
        val adult = Human(20)
        val child = Human(10)
        assertTrue(ReflectionTestUtils.invokeMethod<Boolean>(adult, "isAdult")!!)
        assertFalse(ReflectionTestUtils.invokeMethod<Boolean>(child, "isAdult")!!)
    }

    @Test
    fun test() {
        val adult = Human(20)
        val child = Human(10)

        val method: Method = Human::class.java.getDeclaredMethod("isAdult")
        method.setAccessible(true)
        val adultResult = method.invoke(adult) as Boolean
        val childResult = method.invoke(child) as Boolean

        assertTrue(adultResult)
        assertFalse(childResult)
    }
}

springTest()는 Spring의 ReflectionTestUtils를 사용한 방법이다.

아래의 메서드가 ReflectionTestUtils의 invokeMethod 메서드이다.

targetObject 혹은 targetClass를 넘겨주고, 메서드 이름과 인자를 넘겨주면 해당 메서드를 실행시켜주는 방식으로 구현되어있다.

public class ReflectionTestUtils {

    @Nullable
    public static <T> T invokeMethod(Class<?> targetClass, String name, Object... args) {
        Assert.notNull(targetClass, "Target class must not be null");
        return invokeMethod(null, targetClass, name, args);
    }

    @Nullable
    public static <T> T invokeMethod(@Nullable Object targetObject, @Nullable Class<?> targetClass, String name,
                                     Object... args) {

        Assert.isTrue(targetObject != null || targetClass != null,
                "Either 'targetObject' or 'targetClass' for the method must be specified");
        Assert.hasText(name, "Method name must not be empty");

        try {
            MethodInvoker methodInvoker = new MethodInvoker();
            methodInvoker.setTargetObject(targetObject);
            if (targetClass != null) {
                methodInvoker.setTargetClass(targetClass);
            }
            methodInvoker.setTargetMethod(name);
            methodInvoker.setArguments(args);
            methodInvoker.prepare();

            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Invoking method '%s' on %s or %s with arguments %s", name,
                        safeToString(targetObject), safeToString(targetClass), ObjectUtils.nullSafeToString(args)));
            }

            return (T) methodInvoker.invoke();
        }
        catch (Exception ex) {
            ReflectionUtils.handleReflectionException(ex);
            throw new IllegalStateException("Should never get here");
        }
    }
}

test()는 java의 Reflection을 사용한 방법이다.

public class Class {
    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        @SuppressWarnings("removal")
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
        }
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        return getReflectionFactory().copyMethod(method);
    }
}

이름을 통해 메서드를 찾은 다음 복사하여 메서드를 반환하는 방식으로 구현되어있다.

이후 테스트 코드에서 method.setAccessible(true) 접근이 가능하도록 수정한 후 private 메서드를 동작시킨다.

| Ref. |

https://dev.gmarket.com/38

'IT > Spring' 카테고리의 다른 글

AOP 사용하여 로깅  (0) 2023.09.25
Interceptor를 사용하여 Request/Reponse Logging  (0) 2023.09.20
Annotation Bean register  (0) 2023.09.07
security 6.1 마이그레이션  (1) 2023.05.19
Base64Utils Deprecated  (0) 2023.02.28

댓글