【问题标题】:Final method mocking最终方法模拟
【发布时间】:2011-04-17 03:59:02
【问题描述】:

我需要使用 mockito 模拟一些带有 final 方法的类。我写了这样的东西

@Test
public void test() {
    B b = mock(B.class);
    doReturn("bar called").when(b).bar();   
    assertEquals("must be \"overrided\"", "bar called", b.bar());
    //bla-bla
}


class B {
    public final String bar() {
        return "fail";
    }
}

但它失败了。 我尝试了一些“hack”并且它有效。

   @Test
   public void hackTest() {
        class NewB extends B {
            public String barForTest() {
                return bar();
            }
        }
        NewB b = mock(NewB.class);
        doReturn("bar called").when(b).barForTest();
        assertEquals("must be \"overrided\"", "bar called", b.barForTest());
    }

它有效,但“有味道”。

那么,正确的方法在哪里?

谢谢。

【问题讨论】:

  • 当然,理想的情况是不需要模拟 final 方法。你没有告诉我们你为什么要这么做。我通常会努力保持对接口的依赖...有什么方法可以使用代理到真实类的接口(假设你不能更改类本身)?
  • 这个类来自一些遗留代码。
  • 没有修改选项吗?或者通过另一个实现接口的类来代理它?
  • @Jon Skeet:我无法修改它。你看,它是库(SWT)。有接口IFigure和实现-Figure。但 IFigure 不包含 getLocation()。
  • @Jon Skeet:抱歉,不是 SWT。它是 org.eclipse.draw2d。

标签: java unit-testing testing mocking mockito


【解决方案1】:

Mockito 不支持模拟最终方法。

正如 Jon Skeet 所说,您应该寻找一种方法来避免对 final 方法的依赖。也就是说,有一些方法可以通过字节码操作(例如使用 PowerMock)

comparison between Mockito and PowerMock 会详细解释。

【讨论】:

【解决方案2】:

来自Mockito FAQ

Mockito 有什么限制

  • 无法模拟最终方法 - 它们的真实行为会毫无例外地执行。 Mockito 无法警告您模拟 final 方法,因此请保持警惕。

【讨论】:

【解决方案3】:

你可以将 Powermock 和 Mockito 一起使用,那么你不需要继承 B.class。只需将其添加到您的测试类的顶部

@RunWith(PowerMockRunner.class)
@PrepareForTest(B.class)

@PrepareForTest 指示 Powermock 检测 B.class 以使 final 和 static 方法可模拟。这种方法的一个缺点是您必须使用 PowerMockRunner,这会妨碍使用其他测试运行器,例如 Spring 测试运行器。

【讨论】:

  • 这对我不起作用。按照建议添加,但在不使用@RunWith@PrepareForTest 时仍以相同方式获得NullPointerException。似乎由于某种原因该课程没有受到影响。
  • 你应该用PowerMockito.mock模拟,而不是Mockito.mock
  • Mockito 现在支持 Mockito 2.x 中的 final 方法模拟 github.com/mockito/mockito/wiki/…
【解决方案4】:

我只是做了同样的事情。我的情况是我想确保该方法不会导致错误。但是,由于它是一个 catch/log/return 方法,我无法在不修改类的情况下直接测试它。

我想简单地模拟我传入的记录器。但是,模拟Log 接口的一些方法似乎不起作用,模拟像SimpleLog 这样的类也不起作用,因为这些方法是最终的。

我最终创建了一个匿名内部类,扩展了 SimpleLog,它覆盖了其他人都委托给的基本级别的 log(level, string, error) 方法。然后测试只是在等待level 为 5 的呼叫。

一般来说,为行为扩展一个类并不是一个坏主意,如果不是太复杂的话,它可能比模拟更好。

【讨论】:

    【解决方案5】:

    Mockito 2 现在支持模拟 final 方法,但这是一个“孵化”功能。它需要一些步骤来激活它,如下所述: https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

    【讨论】:

    • 谢谢。你碰巧知道verifying final 方法的当前状态是什么吗?显然我看过那个页面,但它似乎什么也没说......
    • 我不确定。看看这个:stackoverflow.com/questions/14292863/…
    【解决方案6】:

    假设B类如下:

    class B {
        private String barValue;
        public final String bar() {
            return barValue;
        }
        public void final setBar(String barValue) {
            this.barValue = barValue;
        }
    }
    

    有一种更好的方法可以在不使用 PowerMockito 框架的情况下执行此操作。 你可以为你的类创建一个 SPY 并且可以模拟你的 final 方法。 方法如下:

    @Test
    public void test() {
    
        B b  = new B();
        b.setBar("bar called") //This should the expected output:final_method_bar()
        B spyB = Mockito.spy(b);
        assertEquals("bar called", spyB.bar());
    
    }
    

    【讨论】:

      【解决方案7】:

      Mockito 2.x 现在支持 final 方法和 final 类存根。

      From the docs:

      最终类和方法的模拟是一种孵化、选择加入的功能。必须通过创建包含单行的文件 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker 来显式激活此功能:

      mock-maker-inline

      创建此文件后,您可以:

      final class FinalClass {
        final String finalMethod() { return "something"; }
      }
      
      FinalClass concrete = new FinalClass(); 
      
      FinalClass mock = mock(FinalClass.class);
      given(mock.finalMethod()).willReturn("not anymore");
      
      assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());
      

      在随后的里程碑中,团队将带来使用此功能的程序化方式。我们将确定所有不可模拟的场景并提供支持。

      【讨论】:

      • 我用 SpringRunner 尝试了这种方法,并为我想要的特定用例工作,但是在项目中制作所有模拟 inlined 破坏了多个测试项目,它们都使用 MockitoJUnitRunner 运行。如果有人想进一步调查,我可以提供更多细节。现在,我将等到该功能被合并到 Mockito 代码库中,而不更改项目的所有模拟。
      • 请注意,此方法不适用于 Android。 github.com/mockito/mockito/issues/1173
      • 请注意,这可能会减慢您的测试速度
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-29
      • 2018-07-21
      相关资源
      最近更新 更多