1. 概述
在Java中Mock受保护(protected)方法与Mock公共(public)方法类似,但有一个关键区别:测试类中该方法的可见性。受保护方法仅对同包类和其子类可见。如果测试类位于不同包中,就会遇到访问问题。
本文将探讨如何Mock被测类的受保护方法,分别演示有访问权限和无访问权限两种场景。我们将使用Mockito的spy(而非mock),因为只需要部分修改被测类的行为。
2. Mock受保护方法
当测试类能访问受保护方法时,使用Mockito进行Mock非常直接。获取访问权限有两种方式:
- 将
protected
修饰符改为public
- 将测试类移至与目标类相同的包中
但有时这些方案不可行,此时可采用间接方法:
- ✅ 使用JUnit5 + 反射(Reflection)
- ✅ 创建继承目标类的内部测试类
修改访问修饰符可能引发意外行为,遵循最小权限原则是最佳实践。若测试位置可调整,移至同包是最简单的方案。
当类中仅有一个受保护方法需要Mock时,JUnit5+反射是理想选择。若类A有多个受保护方法需要Mock,创建继承A的内部类是更优雅的解决方案。
3. Mock可见的受保护方法
本节处理测试类可访问受保护方法的情况(或可通过修改获得访问)。如前所述,可通过修改访问修饰符为public
或移动测试类位置实现。
以Movies
类为例,其包含受保护方法getTitle()
用于获取私有字段title
的值,以及公共方法getPlaceHolder()
供客户端调用:
public class Movies {
private final String title;
public Movies(String title) {
this.title = title;
}
public String getPlaceHolder() {
return "Movie: " + getTitle();
}
protected String getTitle() {
return title;
}
}
测试类中,首先断言getPlaceholder()
的初始值符合预期。然后使用Mockito spy对受保护方法进行存根(stub),并验证getPlaceholder()
返回的新值包含存根后的getTitle()
值:
@Test
void givenProtectedMethod_whenMethodIsVisibleAndUseMockitoToStub_thenResponseIsStubbed() {
Movies matrix = Mockito.spy(new Movies("The Matrix"));
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");
doReturn("something else").when(matrix).getTitle();
assertThat(matrix.getTitle()).isEqualTo("something else");
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}
4. Mock不可见的受保护方法
当测试类与目标类位于不同包时,无法直接访问受保护方法。此时有两种解决方案:
- JUnit5 + 反射
- 创建继承目标类的内部类
4.1 使用JUnit和反射
JUnit5提供了ReflectionSupport
类,专门处理测试中的常见反射操作(如查找/调用方法等)。以下是基于前述代码的实现:
@Test
void givenProtectedMethod_whenMethodIsVisibleAndUseMockitoToStub_thenResponseIsStubbed() throws NoSuchMethodException {
Movies matrix = Mockito.spy(new Movies("The Matrix"));
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");
ReflectionSupport.invokeMethod(
Movies.class.getDeclaredMethod("getTitle"),
doReturn("something else").when(matrix));
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}
这里通过ReflectionSupport.invokeMethod()
将受保护方法的返回值设置为存根对象。
4.2 使用内部类
通过创建继承目标类的内部类,可使受保护方法变为可见。若多个测试类需要Mock同一受保护方法,可将该内部类定义为独立类。
基于前述代码,在测试类中创建继承Movies
的MoviesWrapper
内部类:
private static class MoviesWrapper extends Movies {
public MoviesWrapper(String title) {
super(title);
}
@Override
protected String getTitle() {
return super.getTitle();
}
}
通过MoviesWrapper
类即可访问Movies
的getTitle()
方法。若使用独立类而非内部类,可能需要将方法修饰符改为public
。
测试中直接使用MoviesWrapper
作为被测类,这样就能轻松访问并Mock getTitle()
方法:
@Test
void givenProtectedMethod_whenMethodNotVisibleAndUseInnerTestClass_thenResponseIsStubbed() {
MoviesWrapper matrix = Mockito.spy(new MoviesWrapper("The Matrix"));
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: The Matrix");
doReturn("something else").when(matrix).getTitle();
assertThat(matrix.getPlaceHolder()).isEqualTo("Movie: something else");
}
5. 总结
本文探讨了在Java中Mock受保护方法时遇到的可见性问题,并提供了多种解决方案。根据实际场景选择合适方案:
- 可见时:直接Mock
- 不可见时:
- 单方法Mock → JUnit5 + 反射
- 多方法Mock → 继承内部类
所有示例代码可在GitHub仓库获取:github.com/eugenp/tutorials