2011年8月15日月曜日

JMockit: 基底クラスのコンストラクタのすりかえ

基底クラスのコンストラクタ呼び出しだけをモックですりかえるにはどうすれば良いか。

以下のように実験してみた。AssertionError を投げているところを、設定ファイルの読み込みとかマスタデータへの依存とか、面倒なセットアップが必要だったり時間がかかったりする処理に読み替えると、ニーズが分かると思う。

====
例題 (1)
クラス BaseClass を継承したクラス CuT があるとする。こんな感じ。
public class CuT extends BaseClass {

public CuT() {
super();
}
}

ところが、BaseClass の実装はこんな感じだったとする。
public class BaseClass {

protected BaseClass() {
throw new AssertionError();
}
}

以下のテストコードを実行すると、当然、AssertionEror が発生してテストが落ちるが、落とさずにインスタンスを作るにはどうすれば良いか?
@Test public void testCuTConstructor() {

//ここに何を書けば落ちないか
new CuT();
}

答え (1)
以下の様にして、基底クラスのコンストラクタをすり替えられる。
@Test public void $init() {

new MockUp<BaseClass>() {
@Mock void $init() { /*とりあえず何もしない*/}
};
new CuT();
}


例題(2)
例題(1) にちょっと書き足してみる。
public class BaseClass {

protected final int bar;
protected BaseClass(int bar) {
this.bar = bar;
throw new AssertionError();
}
}
public class CuT extends BaseClass {
public CuT(int bar) {
super(bar);
}
public int foo() {
return this.bar * 100;
}
}
@Test public void $init2() {
new MockUp<BaseClass>() {
//ここに何も書かないと、下のアサーションが失敗する
};
Assert.assertEquals(200, new CuT(2).foo());
}
このままだと基底クラスの bar が 2 で初期化されず、foo() が 0 * 100 = 0を返してしまうので、アサーションが失敗する。MockUp の匿名インナー型定義に、何を書き足せばテストが通るか?


答え (2)
コンストラクタ実行中のタイミングで、モック対象インスタンスの bar フィールドに適当な値を設定すれば良いわけだが、そのインスタンスをどう取得するかというのが問題。"it"フィールドという仕組みを利用すれば良いらしい。
@Test public void $init2() {

new MockUp<BaseClass>() {
BaseClass it;
@Mock void $init(int bar) {
Deencapsulation.setField(it, bar);
}
};
Assert.assertEquals(200, new CuT(2).foo());
}
この例では、bar は private フィールドなのでDeencapsulation ユーティリティクラスを使っている。ちなみに bar は final だけど、問題なく設定できている。

0 件のコメント:

コメントを投稿