使用mockito测试私有方法

时间:2012-01-10 06:33:11

标签: java junit mockito

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测试私有方法???

12 个答案:

答案 0 :(得分:115)

通过mockito不可能。来自他们的wiki

  

为什么Mockito不会模仿私有方法?

     

首先,我们对于嘲弄私人方法并不是教条主义。我们刚刚   不要关心私人方法,因为从这个角度来看   测试私有方法不存在。这有几个原因   Mockito不会模仿私人方法:

     

它需要对类加载器进行黑客攻击,而这种类加载器绝不是防弹的   更改api(您必须使用自定义测试运行器,注释该类,   等等。)。

     

这很容易解决 - 只需更改方法的可见性   从私有到包受保护(或受保护)。

     

这要求我花时间实施&保持它。它   在第2点和事实已经存在的情况下没有意义   在不同的工具(powermock)中实现。

     

最后......嘲弄私有方法暗示有一些东西   OO理解错误。在OO中,您需要对象(或角色)   合作,而不是方法。忘记pascal&程序代码。认为   在对象中。

答案 1 :(得分:68)

你不能用Mockito做到这一点,但你可以使用Powermock来扩展Mockito和模拟私有方法。 Powermock支持Mockito。 Here就是一个例子。

答案 2 :(得分:26)

以下是如何使用powermock

执行此操作的小示例
public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

要测试 method1 ,请使用代码:

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

答案 3 :(得分:15)

从行为的角度考虑这一点,而不是根据哪些方法。如果method为真,则称为b的方法具有特定行为。如果b为false,则会有不同的行为。这意味着您应该为method编写两个不同的测试;每个案例一个。因此,不是有三个面向方法的测试(一个用于method,一个用于method1,一个用于method2,而是有两个面向行为的测试。

与此相关(我最近在另一个SO线程中提出了这个问题,并且结果被称为四个字母的单词,所以请随意拿出一些盐);我发现选择反映我正在测试的行为的测试名称是有帮助的,而不是方法的名称。因此,请勿调用您的测试testMethod()testMethod1()testMethod2()等等。我喜欢像calculatedPriceIsBasePricePlusTax()taxIsExcludedWhenExcludeIsTrue()这样的名字来表明我正在测试的行为;然后在每个测试方法中,仅测试指示的行为。大多数此类行为只涉及对公共方法的一次调用,但可能涉及对私有方法的多次调用。

希望这有帮助。

答案 4 :(得分:7)

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

    // test方法将是这样...

    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);
    
        }
    }
    
  2. 如果私有方法调用了任何其他私有方法,那么我们需要监视对象并存根另一个方法。测试类将类似于...

    // test方法将是这样...

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

**方法是将反射和监视对象结合起来。 方法1和方法2是私有方法,方法1调用方法2。

答案 5 :(得分:6)

您不应该测试私有方法。只需要测试非私有方法,因为这些方法应该调用私有方法。如果您“想要”测试私有方法,则可能表明您需要重新考虑您的设计:

我使用适当的依赖注入吗? 我是否可能需要将私有方法移动到单独的类中而是测试它? 这些方法必须是私有的吗? ......他们不能默认或受保护吗?

在上面的例子中,两个被称为“随机”的方法实际上可能需要放在自己的类中,经过测试然后注入上面的类。

答案 6 :(得分:5)

我能够使用反射使用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));

答案 7 :(得分:3)

尽管Mockito不提供该功能,但您可以使用Mockito + JUnit ReflectionUtils类或Spring ReflectionTestUtils类来获得相同的结果。请查看下面摘自here的示例,说明如何调用私有方法:

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

Mockito for Spring这本书中可以找到带有ReflectionTestUtils和Mockito的完整示例。

答案 8 :(得分:2)

我真的不明白你需要测试私有方法。根本问题是您的公共方法作为返回类型具有void,因此您无法测试您的公共方法。因此,您被迫测试您的私人方法。我的猜测是否正确?

一些可能的解决方案(AFAIK):

  1. 嘲笑你的私人方法,但你仍然不会“实际”测试你的方法。

  2. 验证方法中使用的对象的状态。 MOSTLY方法要么对输入值进行一些处理并返回输出,要么改变对象的状态。也可以使用测试对象的所需状态。

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

    [这里你可以通过检查classObj从null到not null的状态变化来测试私有方法。]

  3. 稍微重构您的代码(希望这不是遗留代码)。我编写方法的基础是,应该总是返回一些东西(一个int /一个布尔值)。实现可以使用或不使用返回值,但测试将使用它

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

答案 9 :(得分:1)

将测试放在同一个包中,但是使用不同的源文件夹(src / main / java与src / test / java)并将这些方法设置为package-private。 Imo可测试性比隐私更重要。

答案 10 :(得分:0)

实际上,有一种方法可以通过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();会将您可以监视的内容存入私有成员。

答案 11 :(得分:0)

如果private方法不是void并且将返回值用作外部依赖项的方法的参数,则可以模拟该依赖项并使用ArgumentCaptor来捕获返回值。 例如:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");