如何用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做到这一点?

482358 次浏览

你不能用Mockito模拟最后一个类,因为你自己不能这样做。

我所做的是创建一个非final类来包装final类并作为委托使用。一个例子是TwitterFactory类,这是我的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)。

在你的例子中,我会创建一个非final的RainOnTrees类,它委托给final类。或者,如果你能让它变成非最终结果,那就更好了。

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

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

是的,同样的问题在这里,我们不能模拟一个最终类与Mockito。准确地说,Mockito不能模仿/监视以下内容:

  • 最后的课程
  • 匿名类
  • 原始类型

但是在我看来,使用包装器类代价很大,所以改用PowerMockito。

请看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.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

这对我很管用。"SomeMockableType.class"是你想要模拟或监视的对象的父类,而someInstanceThatIsNotMockableOrSpyable是你想要模拟或监视的实际类。

要了解更多细节,请查看在这里

在某些情况下可能适用的另一种解决方法是创建一个由最终类实现的接口,更改代码以使用该接口而不是具体类,然后模拟该接口。这样可以将契约(接口)与实现(最终类)分离。当然,如果您真正想要的是绑定到最终的类,这将不适用。

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

虽然Mockito 不能在设计上模拟final类,但委托方法是可能的. c。这有它的优点:

  1. 如果你的API一开始就打算将你的类改为非final类(final类有它们的好处),那么你不会被迫将你的类改为非final类。
  2. 你在测试API周围装饰的可能性。

在您的测试用例中,您故意将调用转发到被测试的系统。因此,根据设计,你的装饰做做任何事情。

因此,测试还可以证明用户只能修饰API,而不能扩展API。

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

没有尝试final,但私有,使用反射删除修饰符工作!已经进一步检查,它不为最终工作。

我猜你把它做成final是因为你想阻止其他类扩展RainOnTrees。正如有效的Java所建议的(第15项),有另一种方法来保持类接近以进行扩展,而不使其为final:

  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
    }
    }
    

By using this strategy, you'll be able to use Mockito and keep your class closed for extension with little boilerplate code.

实际上有一种方法,我用来监视的。只有满足两个前提条件,它才会为你工作:

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

请回忆有效的Java中的第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类和方法!

但就目前而言,这只是一个“孵化”阶段。特性。它需要一些步骤来激活它,这些步骤在《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的盒子外工作。我建议使用反射来设置测试代码正在使用的对象上的特定字段。如果您发现自己经常这样做,可以将此功能打包到一个库中。

说句题外话,如果你是那个给课程打期末分数的人,那就别这么做了。我遇到这个问题是因为我正在使用一个API,其中所有内容都被标记为final,以防止我合理地需要扩展(mock),我希望开发人员没有假设我永远不需要扩展该类。

如果你正在使用Mockito2,这可以做到,新的孵化功能支持模拟最终类&方法。

< p > 需要注意的要点: < br > 1. 创建一个名为“org.mockito.plugins”的简单文件。并将其放在名为mockito-extensions的文件夹中。这个文件夹应该在类路径上可用 2. 上面创建的文件内容应该是一行,如下所示:
mock-maker-inline < / >强

为了激活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));


}

希望能有所帮助。

完整的文章呈现在这里mocking-the-unmockable

我来跟进一下。请将这一行添加到您的gradle文件:

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

我尝试了不同版本的mockito-core和mockito-all。他们都不工作。

在构建文件中添加以下内容:

  • 如果使用gradle: build.gradle
testImplementation 'org.mockito:mockito-inline:2.13.0'
  • 如果使用maven: pom.xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>2.13.0</version>
<scope>test</scope>
</dependency>

这是一个使mockito与final类一起工作的配置

如果你面对Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)字节的好友依赖项添加到你的build.gradle文件中

testImplementation 'net.bytebuddy:byte-buddy-agent:1.10.19'

src: https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy

为那些在Android + Kotlin上面临相同问题(Mockito + Final Class)的人节省时间。在Kotlin中,默认情况下类是final的。我在谷歌的一个Android样本中找到了一个解决方案。从这里选择的解决方案:https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

创建以下注释:

/**
* 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

修改gradle文件。举个例子:https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

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

如果你试图在测验文件夹下运行unit-test,顶部的解决方案是好的。只要跟着它添加一个扩展。

但如果你想用android相关类运行它,比如在androidtest文件夹下的context或activity, 这个问题的答案适合你。

对我们来说,这是因为我们从koin-test中排除了mockito-inline。一个gradle模块实际上需要这个,并且由于某些原因,它只在发布版本中失败了(IDE中的调试版本可以工作):-P

我认为原则上你需要多想想。相反,你最终类使用他的接口和模拟接口。

:

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


}

在Mockito 3和更多的我有同样的问题,并修复它从这个链接

Mock Final类和方法与Mockito 为遵循< / p >

在Mockito可以用于模拟最终类和方法之前,它需要>配置。

我们需要在项目的src/test/resources/mockito-extensions目录中添加一个名为org.mockito.plugins.MockMaker的文本文件,并添加一行文本:

mock-maker-inline

Mockito在加载时检查扩展目录中的配置文件。该文件允许模拟final方法和类。

对于最终类,添加以下内容以模拟和调用静态或非静态。

1-将此添加到职业级别 @SuppressStatucInitializationFor(value ={带有包的类名})
PowerMockito.mockStatic(classname.class)将模拟class
当调用这个类的方法时,使用你的when语句返回模拟对象

享受

为运行mockito成功添加这些依赖项:

< p > < >强testImplementation org.mockito: mockito-core: 2.24.5的< br > testImplementation“org.mockito: mockito-inline: 2.24.5”< /强> < / p >

根据GitHub问题,模拟最终类是不支持 for mockito-android。您应该使用Mockk代替它。

对于单元测试和ui测试,都可以毫无问题地使用Mockk。

我克服了这条信息:

< p > org.mockito.exceptions.base.MockitoException: 不能模拟/间谍类org.slf4j.impl.Log4jLoggerAdapter Mockito不能模拟/间谍,因为:

  • 最终类或匿名类

log = spy(log);

用这个代替:

log = mock(Logger.class);

这样就有用了。

我猜“违约”;记录器适配器是一个final类的实例,所以我不能“监视”;但我可以嘲笑整件事。去图…

这可能意味着你可以用其他“非终词”来代替它。如果你手边也有这个实例。或者简化版本等等。就其价值而言……

如果你需要在Android的测试中使用Mockito(即在Android设备中运行),你不能使用mockito-inline。有一个特殊的mockito-android版本,它不解决“最终类”;问题。唯一的解决方案,似乎工作是Dexmaker图书馆。唯一的限制是它只能在Android P (Android 9, API 28)或更高版本上运行。导入方式如下:

androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.1"

注意,还有一个&;dexmaker-mockito&;版本,也不适用于最终类。确保您导入了“dexmaker-mockito-inline”。

我正在编写我在Java 11中模拟final/private类及其方法的各种失败尝试之后所遵循的步骤,这些尝试最终对我有用。

    在里面创建一个名为org.mockito.plugins.MockMaker的文件 你的测试/资源/ mockito-extensions文件夹。请创建 Mockito-extensions 文件夹(如果不存在)。
  1. 添加一行mock-maker-inline作为上述org.mockito.plugins.MockMaker文件的内容
  2. 添加
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "jdk.internal.reflect.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*"})
@PrepareForTest(Utility.class)

类级别的注释。

在测试类中设置过程

  @Before
public void setup () {
MockitoAnnotations.initMocks(this);
Mockito.mockStatic(ClassToBeMocked.class);
}
  1. 对断言使用Mockito.when (. .) .thenReturn (. .)
  2. 在多个测试用例的情况下,添加下面的代码
  @After
public void after() {
Mockito.framework().clearInlineMocks();
}

我正在使用的模拟版本:3.9.0 Java版本:11