在正在测试的同一个类中模拟私有方法

我有一个名为MyClass的Java类,我想用JUnit进行测试。 我想测试的公共方法methodB在同一个类中调用私有方法methodB来确定要遵循的条件路径。 我的目标是为methodA的不同路径编写JUnit测试。 此外, methodB调用一个服务,所以我不希望它在运行JUnit测试时实际执行。

模拟methodB并控制其返回的最佳方法是什么,以便我可以测试’methodA’的不同路径?

我更喜欢在编写模拟时使用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)); } } 

但是,我建议这样做。 一般来说, 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来练习该类。 如果这很麻烦,可以考虑将麻烦的代码移到另一个类中,如果它已经不存在,并使用dependency injection来注入麻烦代码的模拟实现。

使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 } } 

就个人而言,我通常只测试公共方法并查看覆盖率报告,以确保在我的私有方法中访问过所有路径。 如果我真的需要测试一个私有方法,那就是我需要重构的气味。

你也可以使用reflection,但我觉得这样做很脏。 如果您真的想要解决方案,请告诉我,我会将其添加到此答案中。

 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(); } } 

要模拟私有方法,您需要powermock
示例代码将是这样的,但我还没有运行它。

  import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.modules.junit4.PowerMockRunner; @RunWith (PowerMockRunner.class) public class MyClassTest { @Test public void test_MyClass_methodA_enters_if_condition() { final MyClass myClass = Mockito.mock (MyClass.class); CustomObject object1 = new CustomObject("input1"); CustomObject object2 = new CustomObject("input2"); Mockito.when (myClass.methodB(object1, object2)).thenReturn (true); Mockito.when (myClass.methodA(object1, object2)).thenCallRealMethod (); assertEquals(myClass.methodA(object1, object2), "Result"); } }