如何使用 Mockito 模拟受保护的方法?

Posted

技术标签:

【中文标题】如何使用 Mockito 模拟受保护的方法?【英文标题】:How do I use Mockito to mock a protected method? 【发布时间】:2016-03-09 15:26:43 【问题描述】:

我使用的是 Mockito 1.9.5。如何模拟从受保护方法返回的内容?我有这个受保护的方法……

protected JSONObject myMethod(final String param1, final String param2)

…

但是,当我尝试在 JUnit 中执行此操作时:

    final MyService mymock = Mockito.mock(MyService.class, Mockito.CALLS_REAL_METHODS);        
    final String pararm1 = “param1”;
    Mockito.doReturn(myData).when(mymock).myMethod(param1, param2);

在最后一行,我收到一个编译错误“方法‘myMethod’不可见。”如果这是答案,我愿意升级我的版本。

【问题讨论】:

【参考方案1】:

使用doReturn() 和Junit5 的ReflectionSupport 对我有用。

[注意:我在 Mockito 3.12.4 上测试过]

ReflectionSupport.invokeMethod(
        mymock.getClass()
//              .getSuperclass()     // Uncomment this, if the protected method defined in the parent class.
                .getDeclaredMethod("myMethod", String.class, String.class),
        doReturn(myData).when(mymock),
        param1,
        param2);

【讨论】:

如果我没看错,这是在调用模拟上的方法。您必须记住的是,mock 通常不是被测类,而是被测类中的依赖项。因此,通常在处理模拟时,您不想调用该方法,而是在被测方法调用模拟时引起所需的行为。也就是说,这样的问题很奇怪,因为如果上述情况属实,为什么要模拟受保护的方法而不是可调用的公共方法。 除非受保护的方法在被测类的超类中,在这种情况下你可以考虑模拟被测类。但同样,您不会在测试类中调用受保护的方法,而是在被测方法中调用。但是,如果我不了解您的解决方案是否有效,请告诉我。 在我的情况下(我正在使用这种技术),我有一个带有受保护方法的超类,我正在测试子类,并且我想模拟对超类方法的调用。在浏览了许多 SO 问题和其他博客之后,我推断出了这种技术。有了这个,我的答案与问题***.com/q/34226209/2367394 更相关(我也在那里发布了答案)。尽管相同的技术可以用于类用户测试,即使受保护/私有方法是在同一个类中定义的,也可以通过监视类来实现。因此,我也在这里发布了答案。 如上所述,我知道对于在被测类中定义的受保护方法,它们应该是直接可模拟的,因为我们通常在定义被测类的同一包中定义测试类。 很有趣,我很高兴被指向 ReflectionSupport。也就是说,在恕我直言的情况下,模仿正在测试的班级仍然感觉像是“测试气味”。我还觉得这段代码太晦涩难懂,以至于 2 年后另一个开发人员会不知道正在做什么。我觉得建议的其他解决方案更具可读性/可维护性:扩展类并覆盖受保护的方法。【参考方案2】:
public class Test extend TargetClass
    @Override
    protected Object method(...) 
        return [ValueYouWant];
      

在 Spring 中,您可以像这样将其设置为 high-high-priority:

@TestConfiguration
public class Config 

    @Profile("...")
    @Bean("...")
    @Primary  // <------ high-priority
    public TargetClass TargetClass()
        return new TargetClass() 
            @Override
            protected WPayResponse validate(...)  
                return null;
            
        ;
    

覆盖origin bean也是一样。

【讨论】:

【参考方案3】:

响应 John B 的回答中对选项 3 的代码示例的请求:


public class MyClass 
    protected String protectedMethod() 
        return "Can't touch this";
    
    public String publicMethod() 
        return protectedMethod();
    


@RunWith(MockitoJUnitRunner.class)
public class MyClassTest 

    class MyClassMock extends MyClass 
        @Override
        public String protectedMethod() 
            return "You can see me now!";
        
    

    @Mock
    MyClassMock myClass = mock(MyClassMock.class);

    @Test
    public void myClassPublicMethodTest() 
        when(myClass.publicMethod()).thenCallRealMethod();
        when(myClass.protectedMethod()).thenReturn("jk!");
    

【讨论】:

@Override 似乎不是强制性的?另外,spy 不是比mock 更合适吗? 我只能使用 Mockito 和 Java 反射来模拟受保护的方法,而无需覆盖任何类/方法。在此处添加答案:***.com/a/69214093/2367394【参考方案4】:

您可以使用 Spring 的 ReflectionTestUtils 来按原样使用您的类,而无需仅仅为了测试或将其包装在另一个类中而对其进行更改。

public class MyService 
    protected JSONObject myProtectedMethod(final String param1, final String param2) 
        return new JSONObject();
    

    public JSONObject myPublicMethod(final String param1) 
        return new JSONObject();
    

然后在测试中

@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest 
    @Mock
    private MyService myService;

    @Before
    public void setUp() 
        MockitoAnnotations.initMocks(this);
        when(myService.myPublicMethod(anyString())).thenReturn(mock(JSONObject.class));
        when(ReflectionTestUtils.invokeMethod(myService, "myProtectedMethod", anyString(), anyString())).thenReturn(mock(JSONObject.class));
    

【讨论】:

你确定这行得通吗?我得到了同样的错误 @tk_ 这会有帮助吗? github.com/hoomb/testdemo 以上答案对我不起作用。但是我只能使用 Mockito 的 doReturn() 方法和 Java 反射来模拟受保护的方法。在此处添加答案:***.com/a/69214093/2367394【参考方案5】:

WhiteBox.invokeMethod() 可以很方便。

【讨论】:

【参考方案6】:

John B 是对的,这是因为您尝试测试的方法是受保护的,这不是 Mockito 的问题。

他列出的另一个选项是使用反射来访问该方法。这将允许您避免更改正在测试的方法,并避免更改用于编写测试的模式以及存储这些测试的位置。对于一些不允许我更改现有代码库的测试,我不得不自己执行此操作,其中包括需要进行单元测试的大量私有方法。

这些链接很好地解释了反射以及如何使用它,所以我将链接到它们而不是复制:

What is reflection and whit is it useful How to test a class that has private methods, fields, or inner classes

【讨论】:

关于您发布的链接,我没有在反射如何与 Mockito 相关联之间建立联系。您能否发布一些示例代码来展示如何使用 Mockito 模拟受保护的方法? 他没有调用受保护的方法(反射会有所帮助),而是试图模拟受保护的方法(它不是)。【参考方案7】:

这不是 Mockito 的问题,而是普通的旧 java。从您调用该方法的位置,您没有可见性。这就是为什么它是编译时问题而不是运行时问题。

几个选项:

在与模拟类相同的包中声明您的测试 如果可以的话,更改方法的可见性 创建一个扩展模拟类的本地(内部)类,然后模拟这个本地类。由于该类是本地的,因此您可以看到该方法。

【讨论】:

我选择了选项 #3(创建一个具有覆盖受保护方法的公共方法的类,它只调用 super.method()) 当包名相同但源代码在主文件夹中并且测试类在测试文件夹中时,方法 1 似乎不起作用 我只能使用 Mockito 和 Java 反射来模拟受保护的方法。在此处添加答案:***.com/a/69214093/2367394

以上是关于如何使用 Mockito 模拟受保护的方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何模拟具有参数的异步受保护方法?

如何在 javascript 类中模拟受保护的变量?

Scala:如何使子类(在其他实例上)可以访问受保护的方法?

如何使用 mockito 模拟方法?

如何使用Mockito正确模拟每个循环?

如何使用 Mockito 框架模拟我的服务