Java 如何用mockito模拟最后一个类
我有最后一节课,类似这样:Java 如何用mockito模拟最后一个类,java,junit,mockito,Java,Junit,Mockito,我有最后一节课,类似这样: public final class RainOnTrees{ public void startRain(){ // some code here } } public class Seasons{ RainOnTrees rain = new RainOnTrees(); public void findSeasonAndRain(){ rain.startRain(); } } 我在
public final class RainOnTrees{
public void startRain(){
// some code here
}
}
public class Seasons{
RainOnTrees rain = new RainOnTrees();
public void findSeasonAndRain(){
rain.startRain();
}
}
我在其他类似的课程中使用此课程:
public final class RainOnTrees{
public void startRain(){
// some code here
}
}
public class Seasons{
RainOnTrees rain = new RainOnTrees();
public void findSeasonAndRain(){
rain.startRain();
}
}
在我的Seasons.java的JUnit测试类中,我想模拟RainContrees
类。我怎样才能用Mockito做到这一点?
将此项添加到gradle文件中:
testImplementation 'org.mockito:mockito-inline:2.13.0'
testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'
这在Mockito v1中是不可能的,从:
Mockito的局限性是什么
- 需要Java1.5+
- 无法模拟最终类
你不能用Mockito模拟最后一个类,因为你不能自己做
我要做的是创建一个非final类来包装final类并用作委托。这方面的一个例子是类,这是我的mockable类:
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)
在您的情况下,我将创建一个非finalraInContrees
类,委托给final类。或者,如果您可以将其设置为非最终版本,则更好。使用Powermock。此链接显示如何执行此操作:我遇到了相同的问题。因为我试图模拟的类是一个简单的类,所以我只是创建了它的一个实例并返回了它。是的,这里有同样的问题,我们不能用Mockito模拟最后一个类。准确地说,Mockito无法模拟/刺探以下内容:
- 期末班
- 匿名类
- 基本类型
但是在我看来,使用包装器类是一个巨大的代价,所以改用PowerMockito。它有大量的文档和大量的例子。这里有一个问题的示例解决方案(为了简化,我在Seasons
中添加了构造函数,以注入模拟的RainContrees
实例):
尝试一下:
Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));
这对我有用。“SomeMockableType.class”是您要模拟或监视的对象的父类,而SomeInstanceThatNotMockableorSpyable是您要模拟或监视的实际类
有关更多详细信息,请查看另一种解决方法,在某些情况下可能适用,即创建由最终类实现的接口,更改代码以使用接口而不是具体类,然后模拟接口。这允许您将契约(接口)与实现(最终类)分开。当然,如果您真正想要绑定到最后一个类,这将不适用。由RC和Luigi R.Viggiano共同提供的解决方案可能是最好的主意
尽管Mockito不能,但根据设计,模拟最终类,委托方法是可能的。这有其优点:
如果API一开始就打算将类更改为非final(final类有自己的特性),那么您不会被迫将类更改为非final
您正在测试围绕您的API进行测试的可能性
在测试用例中,您故意将调用转发到被测试的系统。因此,根据设计,您的装饰没有任何作用
因此,您的测试还可以证明用户只能修饰API,而不能扩展它
更主观地说:
我更喜欢将框架保持在最低限度,这就是为什么JUnit和Mockito通常对我来说就足够了。事实上,限制这种方式有时也会迫使我进行永久性的重构。没有尝试final,但对于private,使用反射移除修改器是有效的!进一步检查后,它对final不起作用。我猜您之所以选择了final
,是因为您想阻止其他类扩展RainOnTrees
。正如所建议的(第15项),还有另一种方法可以使类关闭以进行扩展,而不必使其成为最终类:
删除final
关键字
使其构造函数私有
。没有类能够扩展它,因为它不能调用super
构造函数
创建一个静态工厂方法来实例化类
// 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并使用少量样板代码关闭您的类进行扩展。实际上有一种方法,我用于监视。只有满足以下两个先决条件,它才会对您有效:
您使用某种DI注入最终类的实例
最后一个类实现一个接口
请回顾第16项。您可以创建一个包装器(非final)并将所有调用转发给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();
Mockito 2现在支持final类和方法
但就目前而言,这是一个“孵化”功能。激活它需要一些步骤,如中所述:
模拟最终的类和方法是一种孵化、选择加入的特性。它结合使用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无法做到这一点。我建议使用反射来设置特定的f
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));
}
testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'
testImplementation 'org.mockito:mockito-inline:2.13.0'
testImplementation 'net.bytebuddy:byte-buddy-agent:1.10.19'
/**
* This annotation allows us to open some classes for mocking purposes while they are final in
* release builds.
*/
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass
/**
* Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
*/
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting
apply plugin: 'kotlin-allopen'
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'com.android.example.github.testing.OpenClass'
}
@OpenForTesting
class RepoRepository
public class RainOnTrees{
fun startRain():Observable<Boolean>{
// some code here
}
}
interface iRainOnTrees{
public void startRain():Observable<Boolean>
}
@Before
fun setUp() {
rainService= Mockito.mock(iRainOnTrees::class.java)
`when`(rainService.startRain()).thenReturn(
just(true).delay(3, TimeUnit.SECONDS)
)
}
mock-maker-inline
androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.1"