如何用mockito嘲笑最后一堂课
我有最后一堂课,像这样:
public final class RainOnTrees{ public void startRain(){ // some code here } }
我正在使用这个类,像这样的其他类:
public class Seasons{ RainOnTrees rain = new RainOnTrees(); public void findSeasonAndRain(){ rain.startRain(); } }
在我的JUnittesting类Seasons.java
我想模拟RainOnTrees
类。 我怎样才能用Mockito做到这一点?
只有Mockito v2才能嘲笑final / static类/方法。
从Mockito常见问题解答中 ,Mockito v1是不可能的:
Mockito有什么限制
需要Java 1.5+
不能嘲笑最后的课程
…
Mockito 2现在支持最终的类和方法!
但现在这是一个“孵化”function。 它需要一些步骤来激活它, 这在Mockito 2的新function中有描述:
嘲笑最后的课程和方法是一个孵化 ,selectjoinfunction。 它使用Java代理工具和子类的组合来启用这些types的可模拟性。 由于这与我们当前的机制有所不同,而且这个机制有不同的局限性,而且我们希望收集经验和用户反馈,所以必须明确激活此function。 它可以通过mockito扩展机制完成,通过创build包含一行的文件
src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
:mock-maker-inline
在创build这个文件之后,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());
在随后的里程碑中,团队将带来使用此function的程序化方式。 我们将确定并提供对所有不可debugging场景的支持。 敬请期待,请让我们知道您对此function的看法!
你不能用Mockito嘲笑最后一堂课,因为你不能自己做。
我所做的是创build一个非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)。
在你的情况下,我会创build一个非最终的RainOnTrees
类,委托给最终的类。 或者,如果你能使它不是最终的,那会更好。
使用Powermock。 这个链接显示,如何做到这一点: https : //github.com/jayway/powermock/wiki/MockFinal
试试这个:
Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));
它为我工作。 “SomeMockableType.class”是你想模拟或间谍的父类,someInstanceThatIsNotMockableOrSpyable是你想模拟或窥探的实际类。
有关更多细节,请看这里
我有同样的问题。 由于我试图模拟的类是一个简单的类,我只是简单地创build了一个实例并返回。
另一个可能适用于某些情况的解决方法是创build一个由最终类实现的接口,将代码更改为使用接口而不是具体类,然后模拟接口。 这使您可以将合同(接口)与实施(最终课程)分开。 当然,如果你想要的是真正绑定到最后一堂课,这将不适用。
我想你是final
因为你想阻止其他类扩展RainOnTrees
。 正如Effective Java所build议的(第15项),还有另外一种方法可以保持一个类的扩展性,
-
删除
final
关键字; -
使其构造函数是
private
。 没有类能够扩展它,因为它不能够调用super
构造函数; -
创build一个静态工厂方法来实例化你的类。
// 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,并保持你的类closures扩展与小样板代码。
实际上有一个办法,我用来进行间谍活动。 只有在满足两个前提条件的情况下,它才能起作用。
- 你使用某种types的DI来注入一个final类的实例
- Final类实现了一个接口
请回忆Effective Java中的第16项。 您可以创build一个包装器(不是最终),并将所有调用转发给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,则可以完成此function,并且支持最终类和方法的嘲讽。
要点注意事项:
1.创build一个名为“org.mockito.plugins.MockMaker”的简单文件,并将其放在名为“mockito-extensions”的文件夹中。 这个文件夹应该在类path中可用。
2.上面创build的文件的内容应该是单行,如下所示:
模拟机内联
为了激活mockito扩展机制并使用此selectfunction,需要执行以上两个步骤。
示例类如下:
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 。 它有大量的文件和大量的例子。 在这里你有你的问题的一个例子解决scheme(为了简化我已经添加构造函数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不能模拟/间谍如下:
- 最后一堂课
- 匿名课程
- 原始types
但是使用包装类似乎是一个很大的代价,所以请使用PowerMockito。
RC和Luigi R. Viggiano一起提供的解决scheme可能是最好的主意。
虽然Mockito 不能通过devise模拟最后的课程,但代表团的做法是可能的 。 这有其优点:
- 如果这是您的API首先打算的(最终课程有他们的好处 ),您不会被迫将您的课程更改为非最终课程。
- 您正在testing围绕您的API进行装饰的可能性。
在你的testing案例中,你故意将呼叫转发给被测系统。 因此,在devise上,你的装饰什么都不做。
因此,你的testing也可以certificate用户只能装饰API而不是扩展它。
在一个更主观的说明:我更喜欢保持框架的最低限度,这就是为什么JUnit和Mockito通常足够我。 事实上,限制这种方式有时也会迫使我重构。
正如其他人所说,这不会与Mockito开箱即用。 我会build议使用reflection来设置被测代码正在使用的对象上的特定字段。 如果你发现自己做了很多,你可以将这个function包装在一个库中。
顺便说一句,如果你是标记class最后一名,停止这样做。 我遇到了这个问题,因为我正在使用一个API,在那里所有的事情都被标记为最终的,以防止我的合法需要扩展(嘲笑),并且我希望开发者没有想到我永远不需要扩展这个类。
没有尝试最后,但为私人,使用reflection删除修改过的工作! 它也应该为最后的工作。