この記事ではInjectMocksできない場合の対処法について解説します。
InjectMocksは何でもInjectできるわけではない
実は、InjectMocksがInjectできるのは以下のいづれかでインスタンス生成を行った場合のみなのです。
- コンストラクタインジェクション
- フィールドインジェクション
- セッターインジェクション
これら以外の場合でインスタンスを生成した場合、テストコードでそのオブジェクトにインスタンスを注入することは基本的にできません。
では、順番に解説していきます。
コンストラクタインジェクションの場合
以下のCalc.java
はSubCalc.java
のインスタンス生成をコンストラクタインジェクションにて行っています。
テストコードを実行したときにこのクラスが実行されたことを確認するためにprint文を仕込んでいます。
Calc.java
package products;
public class Calc {
private SubCalc subCalc;
public Calc(){
this.subCalc = new SubCalc();
}
public int add(){
System.out.println("【パターン】コンストラクタインジェクション");
int x = this.subCalc.getValA();
System.out.println("変数x:" + x);
int y = this.subCalc.getValB();
System.out.println("変数y:" + y);
int z = this.subCalc.getValC(x, y);
System.out.println("変数z:" + z);
return x + y + z;
}
}
SubCalc.java
package products;
public class SubCalc {
public int getValA(){
// まだ開発途中
return 1;
}
public int getValB(){
// まだ開発途中
return 1;
}
public int getValC(int x, int y){
// まだ開発途中
return 1;
}
}
テストコードは以下のとおりです。
CalcTest.java
package products;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class CalcTest {
@InjectMocks
private Calc calc;
@Mock
private SubCalc subCalc;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test01(){
// SubCalcクラスの「getValA」メソッドをモックし、実行された場合は3を返す。
Mockito.doReturn(3).when(subCalc).getValA();
// SubCalcクラスの「getValB」メソッドをモックし、実行された場合は4を返す。
Mockito.doReturn(4).when(subCalc).getValB();
// SubCalcクラスの「getValC」メソッドをモックし、実行された場合は5を返す。
Mockito.doReturn(5).when(subCalc).getValC(anyInt(), anyInt());
// 実行して結果を受け取る
int actual = calc.add();
// 期待値の設定
int expected = 12;
// 検証
assertEquals(expected,actual);
}
}
これを実行すると以下の結果になります。
ちゃんと実行できていることが確認できますね。
【パターン】コンストラクタインジェクション
変数x:3
変数y:4
変数z:5
フィールドタインジェクションの場合
以下のCalc.java
はSubCalc.java
のインスタンス生成をフィールドインジェクションにて行っています。
テストコードを実行したときにこのクラスが実行されたことを確認するためにprint文を仕込んでいます。
Calc.java
package products2;
public class Calc {
private SubCalc subCalc = new SubCalc();
public int add(){
System.out.println("【パターン】フィールドインジェクション");
int x = this.subCalc.getValA();
System.out.println("変数x:" + x);
int y = this.subCalc.getValB();
System.out.println("変数y:" + y);
int z = this.subCalc.getValC(x, y);
System.out.println("変数z:" + z);
return x + y + z;
}
}
今回の例ではSubCalc.java
とテストコードの変更はありません。
それではテストコードを実行してみましょう。
以下の結果になるはずです。
ちゃんと実行できていることが確認できますね。
【パターン】フィールドインジェクション
変数x:3
変数y:4
変数z:5
セッタータインジェクションの場合
以下のCalc.java
はSubCalc.java
のインスタンス生成をセッターインジェクションにて行っています。
テストコードを実行したときにこのクラスが実行されたことを確認するためにprint文を仕込んでいます。
Calc.java
package products3;
public class Calc {
private SubCalc subCalc;
public int add(){
System.out.println("【パターン】セッターインジェクション");
int x = this.subCalc.getValA();
System.out.println("変数x:" + x);
int y = this.subCalc.getValB();
System.out.println("変数y:" + y);
int z = this.subCalc.getValC(x, y);
System.out.println("変数z:" + z);
return x + y + z;
}
void setter(SubCalc subCalc){
this.subCalc = subCalc;
}
}
今回の例もSubCalc.java
とテストコードの変更はありません。
それではテストコードを実行してみましょう。
以下の結果になるはずです。
ちゃんと実行できていることが確認できますね。
【パターン】セッターインジェクション
変数x:3
変数y:4
変数z:5
上記以外の場合でインスタンス生成を行った場合
以下の例は、Calc.java
のadd()
メソッド内でsubCalc.java
のインスタンス生成を行っています。
Calc.java
package products4;
public class Calc {
private SubCalc subCalc;
public int add(){
System.out.println("【パターン】ローカルでインスタンス生成");
subCalc = new SubCalc();
int x = subCalc.getValA();
System.out.println("変数x:" + x);
int y = subCalc.getValB();
System.out.println("変数y:" + y);
int z = subCalc.getValC(x, y);
System.out.println("変数z:" + z);
return x + y + z;
}
}
今回の例もSubCalc.java
とテストコードの変更はありません。
それではテストコードを実行してみましょう。
…おやおや?
変数x、変数y、変数zの値がいづれも1になっています。Junitの結果も赤くなっているようです。
【パターン】ローカルでインスタンス生成
変数x:1
変数y:1
変数z:1
対処方法
この問題を解決するには以下の2通りの解決方法があります。
-
案1.
Calc.java
をコンストラクタインジェクション、フィールドインジェクション、セッターインジェクションのいづれかにリファクタリングできるかどうかを検討する。 -
案2.上記の案にできない場合、インスタンスを返却するgetterメソッドを用意し、テストクラス側ではそのgetterメソッドをモックする。
案2のサンプルコードは以下です。
修正後ソース
Calc.java
package products4;
public class Calc {
private SubCalc subCalc;
public int add(){
System.out.println("【パターン】ローカルでインスタンス生成");
subCalc = getInstance();
int x = subCalc.getValA();
System.out.println("変数x:" + x);
int y = subCalc.getValB();
System.out.println("変数y:" + y);
int z = subCalc.getValC(x, y);
System.out.println("変数z:" + z);
return x + y + z;
}
SubCalc getInstance(){
return new SubCalc();
}
}
テストコード
重要なのはcalcに@InjectMocks
だけでなく、@Spy
も付与することです。
CalcTest.java
package products4;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
public class CalcTest {
@InjectMocks
@Spy
private Calc calc;
@Mock
private SubCalc subCalc;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test01(){
//Calcクラスの「getInstance」メソッドをモックし、実行された場合はモックインスタンスのsubCalcを返す。
Mockito.doReturn(subCalc).when(calc).getInstance();
// SubCalcクラスの「getValA」メソッドをモックし、実行された場合は3を返す。
Mockito.doReturn(3).when(subCalc).getValA();
// SubCalcクラスの「getValB」メソッドをモックし、実行された場合は4を返す。
Mockito.doReturn(4).when(subCalc).getValB();
// SubCalcクラスの「getValC」メソッドをモックし、実行された場合は5を返す。
Mockito.doReturn(5).when(subCalc).getValC(anyInt(), anyInt());
// 実行して結果を受け取る
int actual = calc.add();
// 期待値の設定
int expected = 12;
// 検証
assertEquals(expected,actual);
}
}
それではテストコードを実行してみましょう。
以下の結果になるはずです。
ちゃんと実行できていることが確認できますね。
【パターン】ローカルでインスタンス生成
変数x:3
変数y:4
変数z:5
以上で記事の解説はお終い!
もっとJavaやSpringを勉強したい方にはUdemyがオススメ!同僚に差をつけよう!