JUnitでテストしたいJavaクラスMyClass
というクラスがあります。テストしたいパブリックメソッドmethodA
はプライベートを呼び出します同じクラスのメソッドmethodB
を使用して、従う条件付きパスを決定します。私の目標は、methodA
のさまざまなパスに対するJUnitテストを記述することです。また、methodB
は、サービスなので、JUnitテストを実行するときに実際に実行したくありません。
「methodA」のさまざまなパスをテストできるように、methodB
をモックしてその戻りを制御する最良の方法は何ですか?
私はモックを書くときにJMockitを使用することを好むので、JMockitに適用されるすべての回答に特に興味があります。
これが私のサンプルクラスです:
public class MyClass {
public String methodA(CustomObject object1, CustomObject object2) {
if(methodB(object1, object2)) {
// Do something.
return "Result";
}
// Do something different.
return "Different Result";
}
private boolean methodB(CustomObject custObject1, CustomObject custObject2) {
/* For the sake of this example, assume the CustomObject.getSomething()
* method makes a service call and therefore is placed in this separate
* method so that later an integration test can be written.
*/
Something thing1 = cobject1.getSomething();
Something thing2 = cobject2.getSomething();
if(thing1 == thing2) {
return true;
}
return false;
}
}
これは私がこれまでに持っているものです:
public class MyClassTest {
MyClass myClass = new MyClass();
@Test
public void test_MyClass_methodA_enters_if_condition() {
CustomObject object1 = new CustomObject("input1");
CustomObject object2 = new CustomObject("input2");
// How do I mock out methodB here to return true?
assertEquals(myClass.methodA(object1, object2), "Result");
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
CustomObject object1 = new CustomObject("input1");
CustomObject object2 = new CustomObject("input2");
// How do I mock out methodB here to return false?
assertEquals(myClass.methodA(object1, object2), "Different Result");
}
}
ありがとう!
(JMockitの部分的なモックを使用して)要求した答えを与えるには:
public class MyClassTest
{
@Tested MyClass myClass;
@Test
public void test_MyClass_methodA_enters_if_condition() {
final CustomObject object1 = new CustomObject("input1");
final CustomObject object2 = new CustomObject("input2");
new NonStrictExpectations(myClass) {{
invoke(myClass, "methodB", object1, object2); result = true;
}};
assertEquals("Result", myClass.methodA(object1, object2));
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
final CustomObject object1 = new CustomObject("input1");
final CustomObject object2 = new CustomObject("input2");
new NonStrictExpectations(myClass) {{
invoke(myClass, "methodB", object1, object2); result = false;
}};
assertEquals("Different Result", myClass.methodA(object1, object2));
}
}
しかし、私はnotをそのようにすることをお勧めします。一般に、private
メソッドはモックしないでください。代わりに、テスト中のユニットの実際の外部依存関係(この場合はCustomObject
)をモックします。
public class MyTestClass
{
@Tested MyClass myClass;
@Mocked CustomObject object1;
@Mocked CustomObject object2;
@Test
public void test_MyClass_methodA_enters_if_condition() {
new NonStrictExpectations() {{
Something thing = new Something();
object1.getSomething(); result = thing;
object2.getSomething(); result = thing;
}};
assertEquals("Result", myClass.methodA(object1, object2));
}
@Test
public void test_MyClass_methodA_skips_if_condition() {
new NonStrictExpectations() {{
object1.getSomething(); result = new Something();
object2.getSomething(); result = new Something();
}};
assertEquals("Different Result", myClass.methodA(object1, object2));
}
}
モックツールを使用してトリックを行うことができる場合でも、プライベートメソッドをモックするように誘惑されないでください。プライベートメンバーは実装の詳細であり、自由に変更できます。代わりに、プライベートではないAPIを使用してクラスを実行してください。これが問題のある場合は、問題のあるコードを別のクラスに移動することを検討します(まだ存在しない場合)。依存関係注入を使用して、問題のあるコードのモック実装を注入します。
MethodBを別のクラスのメンバーにし、MyClass
内でそのクラスへのプライベート参照を作成します。
public class MyClass {
private MyOtherClass otherObject = new MyOtherClass();
public String methodA(CustomObject object1, CustomObject object2) {
if(otherObject.methodB(object1, object2)) {
// Do something.
return "Result";
}
// Do something different.
return "Different Result";
}
}
class MyOtherClass {
public boolean methodB(CustomObject custObject1, CustomObject custObject2) {
// Yada yada code
}
}
個人的に、私は通常、パブリックメソッドのみをテストし、カバレッジレポートを見て、すべてのパスがプライベートメソッドでアクセスされたことを確認します。私が本当にプライベートメソッドをテストする必要がある場合、それは上記のようにリファクタリングを必要とするにおいです。
リフレクションを使用することもできますが、それを行うのは汚いと思います。あなたが本当にそのための解決策が欲しいなら私に知らせてください、そして私はこの答えにそれを追加します。
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ MyClass.class })
public class MyClassTest {
// Class Under Test
MyClass cut;
@Before
public void setUp() {
// Create a new instance of the service under test (SUT).
cut = new MyClass();
// Common Setup
// TODO
}
@Test
public void testMethodA() throws Exception {
/* Initialization */
CustomObject object2 = PowerMock.createNiceMock(CustomObject.class);
CustomObject object1 = PowerMock.createNiceMock(CustomObject.class);
MyClass partialMockCUT = PowerMock.createPartialMock(MyClass.class,
"methodB");
long response = 1;
/* Mock Setup */
PowerMock
.expectPrivate(partialMockCUT, "methodB",
EasyMock.isA(CustomObject.class),
EasyMock.isA(CustomObject.class)).andReturn(true)
.anyTimes();
/* Mock Setup */
/* Activate the Mocks */
PowerMock.replayAll();
/* Test Method */
String result = partialMockCUT.methodA(object1, object2);
/* Asserts */
Assert.assertNotNull(result);
PowerMock.verifyAll();
}
}