使用Mockito测试私有方法

157
public class A {

    public void method(boolean b){
          if (b == true)
               method1();
          else
               method2();
    }

    private void method1() {}
    private void method2() {}
}

public class TestA {

    @Test
    public void testMethod() {
      A a = mock(A.class);
      a.method(true);
      //how to test like    verify(a).method1();
    }
}
如何测试私有方法是否被调用,以及如何使用 Mockito 测试私有方法?

1
如果你拥有代码库,你应该创建一个公共方法,调用私有方法。如果你没有拥有代码,但你仍然需要为私有方法编写测试(真的!),那么可以使用 PowerMock。https://github.com/powermock/powermock/wiki/MockPrivate - Himesh gosvami
13个回答

155
无法通过Mockito实现。根据他们的维基页面:
为什么Mockito不模拟私有方法?首先,我们对模拟私有方法并不教条主义。我们只是不关心私有方法,因为从测试私有方法的角度来看,私有方法不存在。以下是Mockito不模拟私有方法的几个原因:
它需要破解类加载器,这永远不是绝对可靠的,并且会改变API(您必须使用自定义测试运行程序、注释该类等)。
很容易绕过它——只需将方法的可见性从private更改为package-protected(或protected)。
它要求我花时间实现和维护它。鉴于第2点和它已经在不同的工具中实现了,这是没有意义的(指Mockito不实现私有方法的原因)。
最后......模拟私有方法是OO理解有问题的提示。在OO中,您希望对象(或角色)协作,而不是方法。忘记Pascal和过程式代码,考虑对象。

44
这个语句存在一个致命的假设:>嘲笑私有方法表明对面向对象的理解存在问题。 如果我正在测试一个公共方法,并且它调用了私有方法,我会希望模拟私有方法的返回值。如果按照上述假设,则不需要实现私有方法。那么这怎么会是对面向对象理解的不足之处呢? - eggmatters
8
根据Baeldung的说法,“模拟技术应该应用于类的外部依赖,而不是类本身。如果必须模拟私有方法来测试我们的类,则通常表示设计有问题。“以下是一个很棒关于此主题的讨论帖:https://softwareengineering.stackexchange.com/questions/100959/how-do-you-unit-test-private-methods - Jason Glez
1
如果你将对象视为要测试的东西,这可能是正确的。 但如果你想要测试功能性,那么函数就是你想要使用的东西。函数用于模块化,拥有一个模块意味着必须先对其进行合理程度的验证,然后才验证使用它的任何东西-这可以减轻和提高整体测试结果的质量。 - Alexander Stohr
3
测试私有方法逻辑的面向对象“技巧”是实际上创建新类,将那些私有方法作为公共方法。这样,您可以对新的、更细粒度的类进行单元测试,测试先前的私有逻辑。复杂的私有方法逻辑确实可能表明您的类具有多个职责,并且可能最好将其拆分成更细粒度的类。 - Heschoon

101

使用Mockito无法实现该功能,但您可以使用Powermock来扩展Mockito并模拟私有方法。Powermock支持Mockito。这里是一个示例。


30
我对这个答案感到困惑。这是一种嘲讽的方式,但标题是在测试私有方法。 - diyoda_
我已经使用Powermock来模拟私有方法,但是如何使用Powermock测试私有方法呢?我可以在方法中传递一些输入并期望输出一些结果,然后验证输出吗? - Rito
你不行。你可以模拟输入输出,但无法测试真正的功能。 - Talha
@diyoda_ 作者想要验证方法是否被调用,这必须通过模拟来完成。不幸的是,你只能验证模拟。 - devaga

50

以下是使用PowerMock的一个小例子。

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

测试 方法1 使用代码:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

要设置私有对象 obj,请使用以下代码:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);

你的链接只指向了 power mock 仓库 @Mindaugas - Xavier
@Xavier 确实。如果您喜欢,可以在您的项目中使用它。 - Mindaugas Jaraminas
1
太棒了!!! 用这些简单的例子解释得非常好,几乎包含了所有内容 :) 因为目的只是测试代码,而不是使用框架提供的所有功能 :) - siddhusingh
1
请更新此内容。Whitebox不再是公共API的一部分。 - user447607

45

虽然Mockito不提供这种功能,但是您可以使用Mockito + JUnit ReflectionUtils类或Spring的ReflectionTestUtils类来实现相同的结果。请参见下面的示例,该示例取自此处,说明如何调用私有方法:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ComTurretaSpringReflectionReflectiontestutilsApplicationTests {

    @Test
    public void test01()
    {
        Student student = new Student();
        student.setName("James Dean");

        System.out.println("Before: " + student.toString());
        ReflectionTestUtils.setField(student, "internalCode", 1343243);

        System.out.println("After: " + student.toString());

        ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");
    }
}

使用ReflectionTestUtils和Mockito的完整示例可以在书籍Mockito for Spring中找到。

官方文档Spring Testing


ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "argument1", "argument2", "argument3" ); invokeMethod 的最后一个参数使用了 Vargs,可以传递多个参数到私有方法中。它能够正常工作。 - Tim
3
这个答案应该有更多的赞,这是测试私有方法最简单的方法。 - maxeh
如何知道私有方法是否被调用? - Artanis Zeratul
@ArtanisZeratul 这取决于您的使用情况。通常,如果您想知道是否调用了私有方法,则可以使用Mockito Verify。如果您需要更好的示例,请发布一个问题,我们会在那里解答。 - AR1

25
  1. 通过使用反射,测试类可以调用私有方法。 在这种情况下,

    //测试方法将如下所示...

public class TestA {

  @Test
    public void testMethod() {

    A a= new A();
    Method privateMethod = A.class.getDeclaredMethod("method1", null);
    privateMethod.setAccessible(true);
    // invoke the private method for test
    privateMethod.invoke(A, null);

    }
}
  • 如果私有方法调用了其他任何私有方法,那么我们需要对对象进行间谍操作并存根另一个方法。测试类将会像这样...

    // 测试方法将会是这样的...

  • public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }
    

    这种方法是将反射和对象监视相结合。 **method1和**method2是私有方法,method1调用method2。


    1
    以上提到的工作完美无缺。谢谢。 - Manish
    1
    嗨,Mockito-core:5.1.1和mockito-all:1.10.9中没有这样的方法when(Object, String),你能否指定你使用了哪些依赖项? - velocity

    17

    将其视为行为,而不是方法。 如果btrue,则称为method的方法具有特定的行为。 如果bfalse,则它具有不同的行为。 这意味着您应该为method编写两个不同的测试用例; 每种情况一个。 因此,您只需要两个基于行为的测试,而不是三个基于方法的测试(分别为methodmethod1method2)。

    与此相关的是(我最近在另一个SO线程中提出了这个建议,并因此被称为四个字母的词,所以可以适当参考); 我发现选择反映我正在测试的行为而不是方法名称的测试名称很有帮助。因此,不要将测试命名为testMethod()testMethod1()testMethod2()等。我喜欢像calculatedPriceIsBasePricePlusTax()taxIsExcludedWhenExcludeIsTrue()这样的名称,以表示我正在测试哪些行为; 然后在每个测试方法中,仅测试指定的行为。大多数这样的行为将仅涉及对公共方法的一次调用,但可能涉及对私有方法的多次调用。

    希望这可以帮助您。


    8

    我能够使用反射和mockito测试内部私有方法。 以下是一个示例,试图给它起一个有意义的名称:

    //Service containing the mock method is injected with mockObjects
    
    @InjectMocks
    private ServiceContainingPrivateMethod serviceContainingPrivateMethod;
    
    //Using reflection to change accessibility of the private method
    
    Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
        Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
        //making private method accessible
        m.setAccessible(true); 
        assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));
    

    6
    您不应该测试私有方法。只需要测试非私有方法,因为这些方法应该调用私有方法。如果您“想”测试私有方法,则可能需要重新考虑设计:
    - 我是否使用了正确的依赖注入? - 我可能需要将私有方法移动到单独的类中,并进行测试吗? - 这些方法必须是私有的吗? …… 它们不能是默认或受保护的吗?
    在上面的示例中,被“随机”调用的两个方法实际上需要放置在它们自己的类中进行测试,然后注入到上述类中。

    34
    有道理。但是,使用私有修饰符来修饰方法的原因不就是要减少过长和/或重复的代码吗?将其分离为另一个类就好像你在将这些代码行提升为一等公民,但它们将不会在任何其他地方得到复用,因为它们是专门用于划分冗长的代码并防止重复代码的。如果你要将其拆分为另一个类,这样做感觉不太对;你很容易就会产生类爆炸。 - supertonsky
    2
    注意,supertonsky,我是在指一般情况。我同意在上述情况中它不应该是一个单独的类。(对你的评论点赞,你提出了一个非常有效的关于推广私有成员的观点) - Jaco Van Niekerk
    6
    @supertonsky,我一直无法找到对这个问题的满意答复。有几个原因可能会导致我使用私有成员,而且它们并不总是表示代码存在问题,我会从测试中获得很大帮助。人们似乎总是说“就不要这样做”,来轻描淡写地回避这个问题。 - LuddyPants
    3
    抱歉,我选择根据“如果你‘想要’测试私有方法,这可能意味着你需要贬低你的设计”的观点进行了下投票。好的,很公平,但测试的原因之一是,在截止日期时,当你没有时间重新思考设计时,你正在尝试安全地实现对私有方法的更改。在理想世界中,私有方法不需要更改,因为设计是完美的吗?当然,但在一个完美的世界里,这是无关紧要的,因为在一个完美的世界里,谁需要测试呢,一切都可以正常工作。 - John Lockwood
    2
    将方法移动到它们自己的类中,或者仅仅为了在测试套件中启用功能而使它们公开,这样做绝对是不好的实践。而且,“你不应该测试私有方法。”我强烈不同意。私有方法具有其自身的复杂性,因此完全可以进行测试。 - Luke
    显示剩余2条评论

    4

    实际上,使用Mockito可以测试私有成员的方法。假设你有这样一个类:

    public class A {
        private SomeOtherClass someOtherClass;
        A() {
            someOtherClass = new SomeOtherClass();
        }
        public void method(boolean b){
            if (b == true)
                someOtherClass.method1();
            else
                someOtherClass.method2();
        }
    
    }
    
    public class SomeOtherClass {
        public void method1() {}
        public void method2() {}
    }
    

    如果您想测试 a.method 是否会调用 SomeOtherClass 中的方法,您可以编写以下代码进行测试。
    @Test
    public void testPrivateMemberMethodCalled() {
        A a = new A();
        SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
        ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
        a.method( true );
    
        Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
    }
    

    ReflectionTestUtils.setField();会将私有成员变量设置为你可以进行监视的内容。


    这应该是对于(编辑后的)问题的被接受答案... - Stefan

    3
    我不太理解您需要测试私有方法的原因。根本问题在于您的公共方法返回类型为void,因此您无法测试您的公共方法。因此,您被迫测试您的私有方法。我的猜测正确吗?
    几种可能的解决方案(据我所知):
    1. Mocking your private methods, but still you won't be "actually" testing your methods.

    2. Verify the state of object used in the method. MOSTLY methods either do some processing of the input values and return an output, or change the state of the objects. Testing the objects for the desired state can also be employed.

      public class A{
      
      SomeClass classObj = null;
      
      public void publicMethod(){
         privateMethod();
      }
      
      private void privateMethod(){
           classObj = new SomeClass();
      }
      
      }
      

      [Here you can test for the private method, by checking the state change of the classObj from null to not null.]

    3. Refactor your code a little (Hope this is not a legacy code). My funda of writing a method is that, one should always return something (a int/ a boolean). The returned value MAY or MAY NOT be used by the implementation, but it will SURELY BE used by the test

      code.

      public class A
      { 
          public int method(boolean b)
          {
                int nReturn = 0;
                if (b == true)
                     nReturn = method1();
                else
                     nReturn = method2();
          }
      
          private int method1() {}
      
          private int method2() {}
      
      }
      

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