如何用mockito嘲笑最后一堂课

我有最后一堂课,像这样:

public final class RainOnTrees{ public void startRain(){ // some code here } } 

我正在使用这个类,像这样的其他类:

 public class Seasons{ RainOnTrees rain = new RainOnTrees(); public void findSeasonAndRain(){ rain.startRain(); } } 

在我的JUnit测试类Seasons.java我想模拟RainOnTrees类。 我怎样才能用Mockito做到这一点?

只有Mockito v2才能嘲笑final / static类/方法。

从Mockito常见问题解答中 ,Mockito v1是不可能的:

Mockito有什么限制

  • 需要Java 1.5+

  • 不能嘲笑最后的课程

Mockito 2现在支持最终的类和方法!

但现在这是一个“孵化”功能。 它需要一些步骤来激活它, 这在Mockito 2的新功能中有描述:

嘲笑最后的课程和方法是一个孵化 ,选择加入功能。 它使用Java代理工具和子类的组合来启用这些类型的可模拟性。 由于这与我们当前的机制有所不同,而且这个机制有不同的局限性,而且我们希望收集经验和用户反馈,所以必须明确激活此功能。 它可以通过mockito扩展机制完成,通过创建包含一行的文件src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

 mock-maker-inline 

在创建这个文件之后,Mockito会自动使用这个新的引擎,而且可以这样做:

  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()); 

在随后的里程碑中,团队将带来使用此功能的程序化方式。 我们将确定并提供对所有不可调试场景的支持。 敬请期待,请让我们知道您对此功能的看法!

你不能用Mockito嘲笑最后一堂课,因为你不能自己做。

我所做的是创建一个非final类来包装最终的类并作为委托来使用。 这个例子是TwitterFactory类,这是我的可嘲笑的类:

 public class TwitterFactory { private final twitter4j.TwitterFactory factory; public TwitterFactory() { factory = new twitter4j.TwitterFactory(); } public Twitter getInstance(User user) { return factory.getInstance(accessToken(user)); } private AccessToken accessToken(User user) { return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret()); } public Twitter getInstance() { return factory.getInstance(); } } 

缺点是有很多样板代码; 其优点是可以添加一些与您的应用程序业务相关的方法(例如,在上面的例子中,getInstance是取用户而不是accessToken)。

在你的情况下,我会创建一个非最终的RainOnTrees类,委托给最终的类。 或者,如果你能使它不是最终的,那会更好。

使用Powermock。 这个链接显示,如何做到这一点: https : //github.com/jayway/powermock/wiki/MockFinal

试试这个:

 Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable)); 

它为我工作。 “SomeMockableType.class”是你想模拟或间谍的父类,someInstanceThatIsNotMockableOrSpyable是你想模拟或间谍的实际类。

有关更多细节,请看这里

我有同样的问题。 由于我试图模拟的类是一个简单的类,我只是简单地创建了一个实例并返回它。

另一个可能适用于某些情况的解决方法是创建一个由最终类实现的接口,将代码更改为使用接口而不是具体类,然后模拟接口。 这使您可以将合同(接口)与实施(最终课程)分开。 当然,如果你想要的是真正绑定到最后一堂课,这将不适用。

我想你是final因为你想阻止其他类扩展RainOnTrees 。 正如Effective Java所建议的(第15项),还有另外一种方法可以保持一个类的扩展性,

  1. 删除final关键字;

  2. 使其构造函数是private 。 没有类能够扩展它,因为它不能够调用super构造函数;

  3. 创建一个静态工厂方法来实例化你的类。

     // No more final keyword here. public class RainOnTrees { public static RainOnTrees newInstance() { return new RainOnTrees(); } private RainOnTrees() { // Private constructor. } public void startRain() { // some code here } } 

通过使用这个策略,你将能够使用Mockito,并保持你的类关闭扩展与小样板代码。

实际上有一个办法,我用来进行间谍活动。 只有在满足两个前提条件的情况下,它才能起作用。

  1. 你使用某种类型的DI来注入一个final类的实例
  2. Final类实现了一个接口

请回忆Effective Java中的第16项。 您可以创建一个包装器(不是最终),并将所有调用转发给final类的实例:

 public final class RainOnTrees implement IRainOnTrees { @Override public void startRain() { // some code here } } public class RainOnTreesWrapper implement IRainOnTrees { private IRainOnTrees delegate; public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;} @Override public void startRain() { delegate.startRain(); } } 

现在你不仅可以嘲笑你的最后一堂课,还可以窥探它:

 public class Seasons{ RainOnTrees rain; public Seasons(IRainOnTrees rain) { this.rain = rain; }; public void findSeasonAndRain(){ rain.startRain(); } } IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class) doNothing().when(rain).startRain(); new Seasons(rain).findSeasonAndRain(); 

如果您正在使用Mockito2,则可以完成此功能,并且支持最终类和方法的嘲讽。

要点注意事项:
1.创建一个名为“org.mockito.plugins.MockMaker”的简单文件,并将其放在名为“mockito-extensions”的文件夹中。 这个文件夹应该在类路径中可用。
2.上面创建的文件的内容应该是单行,如下所示:
模拟机内联

为了激活mockito扩展机制并使用此选择功能,需要执行以上两个步骤。

示例类如下:

FinalClass.java

 public final class FinalClass { public final String hello(){ System.out.println("Final class says Hello!!!"); return "0"; } 

}

Foo.java

 public class Foo { public String executeFinal(FinalClass finalClass){ return finalClass.hello(); } 

}

FooTest.java

 public class FooTest { @Test public void testFinalClass(){ // Instantiate the class under test. Foo foo = new Foo(); // Instantiate the external dependency FinalClass realFinalClass = new FinalClass(); // Create mock object for the final class. FinalClass mockedFinalClass = mock(FinalClass.class); // Provide stub for mocked object. when(mockedFinalClass.hello()).thenReturn("1"); // assert assertEquals("0", foo.executeFinal(realFinalClass)); assertEquals("1", foo.executeFinal(mockedFinalClass)); } 

}

希望它有帮助。

完整的文章在这里嘲笑的unmockable 。

只是跟进。 请将此行添加到您的Gradle文件中:

 testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9' 

我已经尝试过各种版本的mockito-core和mockito-all。 他们都没有工作。

请看JMockit 。 它有大量的文件和大量的例子。 在这里你有你的问题的一个例子解决方案(为了简化我已经添加构造函数Seasons注入RainOnTrees实例):

 package jmockitexample; import mockit.Mocked; import mockit.Verifications; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(JMockit.class) public class SeasonsTest { @Test public void shouldStartRain(@Mocked final RainOnTrees rain) { Seasons seasons = new Seasons(rain); seasons.findSeasonAndRain(); new Verifications() {{ rain.startRain(); }}; } public final class RainOnTrees { public void startRain() { // some code here } } public class Seasons { private final RainOnTrees rain; public Seasons(RainOnTrees rain) { this.rain = rain; } public void findSeasonAndRain() { rain.startRain(); } } } 

同样的问题在这里,我们不能用Mockito嘲笑最后一堂课。 为了准确,Mockito不能模拟/间谍如下:

  • 最后一堂课
  • 匿名课程
  • 原始类型

但是使用包装类似乎是一个很大的代价,所以请使用PowerMockito。

RC和Luigi R. Viggiano一起提供的解决方案可能是最好的主意。

虽然Mockito 不能通过设计模拟最后的课程,但代表团的做法是可能的 。 这有其优点:

  1. 如果这是您的API首先打算的(最终课程有他们的好处 ),您不会被迫将您的课程更改为非最终课程。
  2. 您正在测试围绕您的API进行装饰的可能性。

在你的测试案例中,你故意将呼叫转发给被测系统。 因此,在设计上,你的装饰什么都不做。

因此,你的测试也可以证明用户只能装饰API而不是扩展它。

在一个更主观的说明:我更喜欢保持框架的最低限度,这就是为什么JUnit和Mockito通常足够我。 事实上,限制这种方式有时也会迫使我重构。

正如其他人所说,这不会与Mockito开箱即用。 我会建议使用反射来设置被测代码正在使用的对象上的特定字段。 如果你发现自己做了很多,你可以将这个功能包装在一个库中。

顺便说一句,如果你是标记班最后一名,停止这样做。 我遇到了这个问题,因为我正在使用一个API,在那里所有的事情都被标记为final,以防止我合法的扩展(嘲讽)需求,而且我希望开发者不要假设我永远不需要扩展这个类。

没有尝试最后,但为私人,使用反射删除修改过的工作! 它也应该为最后的工作。