通过JMockit调用私有方法以测试结果

18

我正在使用JMockit 1.1,我想要做的就是调用一个私有方法并测试其返回值。但是,我不太明白如何从JMockit De-Encapsulation示例中实现这一点。

我试图测试的方法是这个类中的私有方法:

public class StringToTransaction {
   private List<String> parseTransactionString(final String input) {
      // .. processing
      return resultList;
   }
}

以下是我的测试代码。

@Test
public void testParsingForCommas() {
   final StringToTransaction tested = new StringToTransaction();
   final List<String> expected = new ArrayList<String>();
   // Add expected strings list here..
   new Expectations() {
      {
         invoke(tested, "parseTransactionString", "blah blah");
         returns(expected);
      }
   };
}

我收到的错误信息是:

java.lang.IllegalStateException: 在此处缺少对模拟类型的调用;请确保这种调用仅出现在适当的模拟字段或参数的声明之后

也许我误解了整个API,因为我不认为我想要模拟这个类..只是测试调用私有方法的结果。

6个回答

39

我认为你让事情变得太复杂了。你根本不需要使用Expectations语句块。你只需要像这样做:

@Test
public void testParsingForCommas() {
   StringToTransaction tested = new StringToTransaction();
   List<String> expected = new ArrayList<String>();
   // Add expected strings list here..

   List<String> actual = Deencapsulation.invoke(tested, "parseTransactionString", "blah blah");
   assertEquals(expected, actual);
}

基本上,通过使用Deencapsulation调用私有方法,并测试实际结果是否等于预期结果。就像您如果方法是公共的一样。不进行任何模拟,因此不需要Expectations块。


嗨@JeffOlson - invoke()方法在哪个类中?在Expectations中,invoke()是一个受保护的实例方法。或者只需使用java.lang.reflect.Method.invoke()并忽略JMockit即可。 - Robert Mark Bram
终于我明白了...它是Deencapsulation.invoke,感谢@JeffOlson以及在https://groups.google.com/d/msg/jmockit-users/oEgjW0DfmgU/3pEE1mjt1ncJ中的Rogerio。 - Robert Mark Bram
是的,没错。很抱歉我没有表达得更清楚,但你在最初的例子中直接使用了invoke()(可能通过静态导入),所以我认为我应该保持原样。我会更新我的答案,以便未来任何其他人阅读时更加清晰明了。 - Jeff Olson
我在这里写了这篇文章:http://robertmarkbramprogrammer.blogspot.com.au/2013/03/testing-private-method-through.html - Robert Mark Bram
@JeffOlson 在最新的jmockit中,他们已经移除了这个功能。那么有没有其他方法可以实现相同的行为呢?这个答案需要更新。 - IfOnly
是的,看起来你是正确的。我建议查看https://dev59.com/unNA5IYBdhLWcg3wpfmu上的一些答案。具体来说,Spring的ReflectionUtils看起来很有趣。 - Jeff Olson

3

目前,我不确定是否可以或应该使用JMockit来进行测试。测试我的私有方法可以使用普通的反射来完成,尽管我开始这个练习是为了学习JMockit(并且测试我的代码)。如果JMockit不能用于此,则可以使用以下方式来使用反射。

@Test
public void testParsingForCommas() throws Exception {
   StringToTransaction tested = new StringToTransaction();
   ArrayList<String> expected = new ArrayList<>();
   expected.add("Test");

   Method declaredMethod =
         tested.getClass().getDeclaredMethod("parseTransactionString",
               String.class);
   declaredMethod.setAccessible(true);
   Object actual = declaredMethod.invoke(tested, "blah blah");
   assertEquals(expected, actual);
}

在这里,调用setAccessible(true)是很重要的,否则,在调用私有方法时,invoke会崩溃。

declaredMethod.setAccessible(true);

但你想知道什么真正酷的事情吗?如果你没有调用setAccessible(true),它将会出现一个java.lang.StackOverflowError错误!:)


2

由于最新的Jmockit不允许模拟私有方法,因此可以使用内部API的模拟作为解决方法,而不是模拟私有方法。

这个解决方法也可以被视为最终解决方案。

示例:
实际类:

class A {

  private int getId(String name){  //private method
      return DAOManager.getDao().getId(name);  //Call to non-private method can be mocked.
  }
}  

测试类:

public class ATest{

  @Before
  public void setUp(){
    new MockDAOManager();
  }

  //Mock APIs used by the private method `getId`.
  public static class MockDAOManager extends MockUp<MockDAOManager>{
     static mocked_user_id = 101;

     @Mock
     public DAOManager getDao() throws Exception{
          return new DAOManager();
     }

     @Mock
     public Integer getId(String name){
         return mocked_user_id;
     }
  }
}

注意:

  • 如果您没有这样的逻辑(私有方法调用其他非私有方法),那么您可能需要重构您的代码,否则这将无法工作。
  • 这里的DAOManager.getDao().getId(name)不是一个私有API。
  • 可能需要模拟该私有方法使用的所有API。

0

从1.35版本开始,jmockit移除了该辅助方法。原因是它不再有用(我不太理解为什么)

但是,是的,这个实用程序在其他地方可用。

org.springframework.test.util.ReflectionTestUtils

0

正如 @Jeff Olson 所提到的,您也可以通过声明 @Tested 来调用 bean 的私有方法。

以下是一个示例:

// Java

    @Tested
    private YourServiceImplClass serviceImpl;

    @Test
    public void testPrivateMethod() {
   
          List<String> expected = new ArrayList<String>();
          // Add expected strings list here..

          List<String> actual = Deencapsulation.invoke(serviceImpl, "yourPrivateMethod", "arguments");
          assertEquals(expected, actual);
    }

-2
为什么您想要直接测试私有方法?大多数情况下,API方法,即公共接口方法被单元测试,因为私有方法也会间接地被测试。您可以在公共方法中调用它们时放置带有来自私有方法的预期值的断言语句。因此,如果断言失败,您可以确定私有方法存在问题。所以您不需要单独对其进行测试。

1
嗨@Ankur,我很抱歉,但我正在寻找一个涉及如何在这种情况下正确使用JMockit的答案(甚至是否可以)。我不想讨论为什么我应该或不应该测试私有方法。我知道通过公共方法使用断言是另一种方法,但这不是我想解决的问题。 - Robert Mark Bram
看一下这个:http://stackoverflow.com/questions/5729973/jmockit-two-mocked-instances-of-the-same-type - Ankur Shanbhag
我可能有点迟钝,@Ankur,但我无法弄清楚如何将该解决方案应用于我的问题。 :) 将Expectations块放在tested初始化之前会导致编译错误:无法将tested解析为变量 - Robert Mark Bram
我在网上寻找一些解决方案。我找到了一个好的链接,请查看它。http://rockycode.com/blog/easymock-cause-effect-exception-mapping/ - Ankur Shanbhag
那是使用EasyMock,而不是JMockit @Ankur。它们的使用模式似乎有很大的不同。 - Robert Mark Bram
1
有时候,我发现测试私有方法比依赖公共方法的测试覆盖率更有意义。 - Jeff Olson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接