【问题标题】:How do I use Mockito to mock a protected method?如何使用 Mockito 模拟受保护的方法?
【发布时间】: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’不可见。”如何使用 Mockito 模拟受保护的方法?如果这是答案,我愿意升级我的版本。

【问题讨论】:

    标签: junit mocking mockito protected


    【解决方案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 问题和其他博客之后,我推断出了这种技术。有了这个,我的答案与问题stackoverflow.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 反射来模拟受保护的方法,而无需覆盖任何类/方法。在此处添加答案:stackoverflow.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));
          }
      }
      

      【讨论】:

      【解决方案5】:

      WhiteBox.invokeMethod() 可以很方便。

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

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

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

        几个选项:

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

        【讨论】:

        • 我选择了选项 #3(创建一个具有覆盖受保护方法的公共方法的类,它只调用 super.method())
        • 当包名相同但源代码在主文件夹中并且测试类在测试文件夹中时,方法 1 似乎不起作用
        • 我只能使用 Mockito 和 Java 反射来模拟受保护的方法。在此处添加答案:stackoverflow.com/a/69214093/2367394
        猜你喜欢
        • 2016-05-29
        • 1970-01-01
        • 1970-01-01
        • 2012-01-08
        • 2022-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多