1. 概述

在Java中Mock受保护(protected)方法与Mock公共(public)方法类似,但有一个关键区别:测试类中该方法的可见性。受保护方法仅对同包类和其子类可见。如果测试类位于不同包中,就会遇到访问问题。

本文将探讨如何Mock被测类的受保护方法,分别演示有访问权限和无访问权限两种场景。我们将使用Mockito的spy(而非mock),因为只需要部分修改被测类的行为。

2. Mock受保护方法

当测试类能访问受保护方法时,使用Mockito进行Mock非常直接。获取访问权限有两种方式:

  1. protected修饰符改为public
  2. 将测试类移至与目标类相同的包中

但有时这些方案不可行,此时可采用间接方法:

  • 使用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同一受保护方法,可将该内部类定义为独立类。

基于前述代码,在测试类中创建继承MoviesMoviesWrapper内部类:

private static class MoviesWrapper extends Movies {
    public MoviesWrapper(String title) {
        super(title);
    }

    @Override
    protected String getTitle() {
        return super.getTitle();
    }
}

通过MoviesWrapper类即可访问MoviesgetTitle()方法。若使用独立类而非内部类,可能需要将方法修饰符改为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


原始标题:Mocking Protected Method in Java | Baeldung