Mockitoでfinalクラスをモック化
※解釈が正しいかは微妙なのであしからず
通常Mockitoではfinalクラス等はモック化出来ない。やってもエラーになるのだけど、それを無茶ぶりでやっちゃう方法がある模様
- ClassLoaderを継承したクラスを作る。loadClassをオーバーライドしてモック化を対象とするクラスのターゲットにfinal修飾子を削除したクラスを定義させてそのClassを返すように実装
- Mockito.mockで指定するClassを上記のClassLoader経由で取得する。
っていう工程を行う事で可能らしい。後述する参考ではPowerMockitoを使ってる模様。とりまぁやってみるか
モックするクラス
package sample;
public interface Sample {
String say();
}
なインターフェースがあって
package sample;
public final class SampleImpl implements Sample {
public String say() {
return "hoge";
}
}
というようなクラスを対象にする
ClassLoaderを継承したクラスを作成
package sample;
import java.lang.reflect.Modifier;
import javassist.ClassPool;
import javassist.CtClass;
public class SampleClassLoader extends ClassLoader {
private ClassPool pool;
public SampleClassLoader() {
pool = ClassPool.getDefault();
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.equals("sample.SampleImpl")) {
return findClass(name);
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] b = null;
try {
CtClass clazz = pool.get(name);
clazz.setModifiers(clazz.getModifiers() & ~Modifier.FINAL);
b = clazz.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
if (b == null) {
throw new ClassNotFoundException();
}
return defineClass(name, b, 0, b.length);
}
}
利用するにはjavassistが必要
テストを書く
package sample;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
public class SampleTestCase {
private ClassLoader classLoader;
@Before
public void setUp() throws Exception {
classLoader = new SampleClassLoader();
}
@SuppressWarnings("unchecked")
@Test
public void test1() throws Exception {
Class<SampleImpl> clazz = (Class<SampleImpl>)classLoader.loadClass("sample.SampleImpl");
Sample sample = (Sample)mock(clazz);
when(sample.say()).thenReturn("fuga");
assertThat(sample.say(), is("hoge")); // fail
}
}
っていう感じ。で
when(sample.say()).thenCallRealMethod();
とかにしとけばテストは通る。でテストスタックトレース的には
java.lang.AssertionError: Expected: is "hoge" but: was "fuga"
んで
Sample sample = (Sample)mock(SampleImpl.class);
なんていうのをやるとどうなるか
org.mockito.exceptions.base.MockitoException: Cannot mock/spy class sample.SampleImpl Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
というようにfinal classはmock出来ねーよって怒られる。っていうような感じでfinalクラスをmock化する方法があったのでやってみた程度で
参考: http://www.objectpartners.com/2012/03/15/how-to-mock-final-classes-in-unit-tests
追記
続きとなる別アプローチを書きました