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

55

我正在使用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’不可见”。我该如何使用Mockito模拟受保护的方法?如果那是答案,我愿意升级我的版本。

7个回答

58

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

有几个选项:

  • 将测试声明为与模拟类相同的包中
  • 如果可以的话,更改方法的可见性
  • 创建一个继承模拟类的本地(内部)类,然后模拟这个本地类。由于该类是本地的,因此您将会有对该方法的可见性。

6
我选择了第三个选项(创建一个类,其中包含一个公共方法,该方法覆盖了受保护的方法,只需调用 super.method())。 - Dave
4
当包名相同但源代码位于主文件夹,而测试类位于测试文件夹中时,方法1似乎无法正常工作。 - poyger
我能够使用Mockito和Java反射来模拟受保护的方法。在此处添加答案:https://dev59.com/glsX5IYBdhLWcg3wNtPA#69214093 - Debanshu Kundu
对于问题#3,如果模拟的类在其构造函数中调用另一个超类会怎样? - Saurav Shrivastav
@SauravShrivastav 我认为它仍然可以工作,因为它能够创建实例。你试过了吗?你有错误吗? - John B

25

回应 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!");
    }
}

1
“@Override” 似乎不是必须的?此外,使用“spy”比“mock”更合适吧? - Skippy le Grand Gourou
我能够使用Mockito和Java反射来模拟受保护的方法,而无需覆盖任何类/方法。在此处添加答案:https://dev59.com/glsX5IYBdhLWcg3wNtPA#69214093 - Debanshu Kundu

15
您可以使用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_
1
@tk_ 这个对你有帮助吗?https://github.com/hoomb/testdemo - hoomb
1
上面的答案对我没有用。但是我能够使用Mockito的doReturn()方法和Java反射来模拟受保护的方法。在这里添加了答案:https://dev59.com/glsX5IYBdhLWcg3wNtPA#69214093 - Debanshu Kundu

4

以下类似的方法对我有效,使用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);

如果我理解正确的话,这将在模拟对象上调用方法。你必须记住的是模拟对象通常不是被测试的类,而是被测试类的一个依赖项。因此,一般情况下,在处理模拟对象时,你不希望调用方法,而是在被测试的方法调用模拟对象时引发期望的行为。话虽如此,这个问题很奇怪,因为如果以上是正确的,那么为什么你要模拟一个受保护的方法而不是可调用的公共方法呢? - John B
除非受保护的方法在测试类的超类中,否则您可以考虑对测试类进行模拟。但是,再次强调,在测试类中不会调用受保护的方法,而是在被测试的方法中调用。如果我的理解有误,请告诉我您的解决方案如何工作。 - John B
1
在我的情况下(我正在使用这种技术),我有一个带有受保护方法的超类,我正在测试子类,并且我想模拟对超类方法的调用。在查看了许多SO问题和其他博客之后,我推断出了这种技术。通过这种方式,我的答案与问题更相关 https://dev59.com/X5Hea4cB1Zd3GeqPwPVu(我也在那里发布了答案)。尽管相同的技术可以用于类用户测试,即使受保护/私有方法在同一类中定义,也可以通过对该类进行监视来实现。因此,我也在这里发布了答案。 - Debanshu Kundu
尽管如上所述,我明白在测试类中定义的受保护方法应该直接可模拟,因为我们通常将测试类定义在与被测试类相同的包中。 - Debanshu Kundu
有趣的是,我很感激被指向ReflectionSupport。尽管如此,在我看来,模拟测试类下的“测试气味”仍然存在。我还觉得这段代码非常晦涩,两年后再看这段代码的另一个开发人员可能完全不知道正在做什么。我认为其他建议的解决方案更易读/可维护:扩展该类并覆盖受保护的方法。 - John B

1
约翰B是正确的,这是因为您尝试测试的方法受到保护,这与Mockito无关。
除了他列出的选项之外,另一个选择是使用反射来访问该方法。这将允许您避免更改您正在测试的方法,并避免更改编写测试和存储这些测试的模式。我自己曾经在一些测试中不允许更改现有代码库的情况下做过这件事,其中包括需要进行单元测试的大量私有方法。
这些链接非常好地解释了反射以及如何使用它,因此我将链接到它们而不是复制:
- 什么是反射以及它有什么用处 - 如何测试具有私有方法、字段或内部类的类

关于您发布的链接,我没有看出反射与Mockito之间的联系。您能否提供一些示例代码,展示如何使用Mockito模拟受保护的方法? - Dave
他并没有调用受保护的方法(这时反射会很有帮助),而是试图模拟受保护的方法(这时反射就不适用了)。 - John B

0

WhiteBox.invokeMethod() 可以很方便。


-1
public class Test extend TargetClass{
    @Override
    protected Object method(...) {
        return [ValueYouWant];
    }  
}

在Spring中,你可以这样将它设置为高优先级:
@TestConfiguration
public class Config {

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

覆盖原始bean是相同的。


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