はじめに
時々単体をJUnitでやるけど、いちいちプライベートメソッドを実行する処理を作るのが面倒なのでここにメモ。テストに限った話ではないですがおそらくテストコードにしか使わなそう。
privateメソッドを実行するメソッド
Params.java
package com.example.demo.util;
import java.lang.reflect.Method;
public class ReflectionUtil {
/**
* プライベートメソッドを実行する(引数なし).
* @param obj メソッドを実行するインスタンス
* @param methodName メソッド名
* @param params 引数
* @return メソッドの戻り値
* @throws Exception
*/
public static Object invokePrivate(Object obj, String methodName) throws Exception {
Class<?> clazz = obj.getClass();
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(obj);
}
/**
* プライベートメソッドを実行する(引数あり).
* @param obj メソッドを実行するインスタンス
* @param methodName メソッド名
* @param params 引数
* @return メソッドの戻り値
* @throws Exception
*/
public static Object invokePrivate(Object obj, String methodName, Params params) throws Exception {
if(params == null ){
throw new NullPointerException("params is required");
}
Class<?> clazz = obj.getClass();
Method method = clazz.getDeclaredMethod(methodName, params.getTypes());
method.setAccessible(true);
return method.invoke(obj, params.getArgs());
}
}
ちなみに、getDeclaredMethodはjava.lang.Classオブジェクトからメソッドオブジェクトを取得するので、staticメソッドの場合はインスタンス化する必要はないです。なのでinvokePrivateの第一引数にクラスを直接渡してgetDeclaredMethodを実行すればOK。
引数をまとめるクラス
上記の引数ありprivateメソッドを実行するために必要な引数をまとめるためのクラス。
※一部lombok使用。
Params.java
package com.example.demo.util;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
public class Params {
private List<Param> paramList;
public Params() {
paramList = new ArrayList<>();
}
/**
* 初期化と同時に引数指定するコンストラクタ.
* @param type
* @param arg
*/
public <T> Params(Class<T> type, T arg) {
this();
add(type, arg);
}
/**
* 引数とその型を設定.
* @param type
* @param arg
*/
public <T> void add(Class<T> type, T arg) {
paramList.add(new Param(type, arg));
}
/**
* 設定した引数の型を配列で返す.
* @return
*/
public Class<?>[] getTypes() {
Class<?>[] types = new Class[paramList.size()];
for (int i = 0; i < paramList.size(); i++) {
types[i] = paramList.get(i).getType();
}
return types;
}
/**
* 設定した引数を配列で返す.
* @return
*/
public Object[] getArgs() {
Object[] args = new Object[paramList.size()];
for (int i = 0; i < paramList.size(); i++) {
args[i] = paramList.get(i).getArg();
}
return args;
}
/**
* 引数1つを表すクラス.<br />
* 引数オブジェクトとその型を属性に持つ。
*
*/
@Data
private class Param {
private Class<?> type;
private Object arg;
<T> Param(Class<T> type, T arg) {
this.type = type;
this.arg = arg;
}
}
}
実行してみる
- テスト対象privateメソッド
- テストケース
- 実行結果
テスト対象privateメソッド
引数を持つprivateメソッドを適当に用意。
Test.java
private String doSomthing(int i, Long l, String str){
System.out.println(Integer.toString(i));
System.out.println(l.toString());
System.out.println(str);
return "完了!";
}
テストケース
UtilTest.java
@Test
public void test1() {
AService test = new AService();
// コンストラクタで1個目の引数を指定してみる
Params params = new Params(int.class, 1);
// あとはaddで追加
params.add(Long.class, 20L);
params.add(String.class, "3つ目の引数");
try {
// 実行
String ret = (String) ReflectionUtil.invokePrivate(test, "doSomthing", params);
System.out.println(ret);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
実際に使うときはinvokePrivateはstaticインポートした方が見やすいですね。
実行結果
実行結果
1
20
3つ目の引数
完了!
ちなみに、
UtilTest.java
/**
* paramsの代わりにnullを指定した場合.
*/
@Test
public void testParamsNull() {
AServices = new AService();
try {
ReflectionUtil.invokePrivate(s, TARGET_METHOD, null);
fail();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* paramsに設定した引数が一致しなかった場合.
*/
@Test
public void testParamsInvalid() {
AService s = new AService();
Params params = new Params(int.class, 1);
params.add(Long.class, 20L);
try {
ReflectionUtil.invokePrivate(s, TARGET_METHOD, params);
fail();
} catch (Exception e) {
e.printStackTrace();
}
}
実行結果 testParamsNull()
java.lang.NullPointerException: params is required
at com.example.demo.util.ReflectionUtil.invokePrivate(ReflectionUtil.java:32)
at com.example.demo.UtilTest.test1(UtilTest.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
:
実行結果 testParamsInvalid()
java.lang.NoSuchMethodException: com.example.demo.service.AService.doSomthing(int, java.lang.Long)
at java.lang.Class.getDeclaredMethod(Class.java:2122)
at com.example.demo.util.ReflectionUtil.invokePrivate(ReflectionUtil.java:35)
at com.example.demo.UtilTest.test1(UtilTest.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
:
以上。