ChatGPT解决这个技术问题 Extra ChatGPT

如何用 mockito 模拟最后一堂课

我有最后一堂课,是这样的:

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 测试类中,我想模拟 RainOnTrees 类。我怎么能用 Mockito 做到这一点?

Mockito 不允许,但 PowerMock 允许。
从 Mockito 2.x 开始,Mockito 现在支持模拟最终类和方法。
Mock final class with Mockito 2 的可能重复项

C
Community

Mockito 2 现在支持 final 类和方法!

但就目前而言,这是一个“孵化”功能。它需要一些步骤来激活它,如 What's New in 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 具体 = new FinalClass(); FinalClass 模拟 = 模拟(FinalClass.class); given(mock.finalMethod()).willReturn("不再"); assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());在随后的里程碑中,团队将带来使用此功能的程序化方式。我们将为所有不可模拟的场景识别并提供支持。请继续关注,请让我们知道您对此功能的看法!


我仍然收到一个错误:无法模拟/间谍类 android.content.ComponentName Mockito 无法模拟/间谍,因为:-最终类
确保将 org.mockito.plugins.MockMaker 文件放在正确的文件夹中。
即使遵循上述内容,我也收到错误消息:Mockito cannot mock/spy because : - final class
@vCillusion 这个答案与 PowerMock 没有任何关系。
我遵循了这些说明,但我仍然无法完成这项工作,是否有人需要做其他事情?
a
akshay bhange

Mocking final/static classes/methods is possible with Mockito v2 only.

将此添加到您的 gradle 文件中:

testImplementation 'org.mockito:mockito-inline:2.13.0'

这对于来自 Mockito FAQ 的 Mockito v1 是不可能的:

Mockito Needs java 1.5+ 有什么限制不能模拟最终类...


这在 Scala 中对我不起作用(经过 sbt 修改)。
这对我来说还不够。我还必须按照 baeldung.com/mockito-final 创建带有“mock-maker-inline”的 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
Mockito 指南建议用 mockito-core “替换”,而不是“添加”,mockito-inlinejavadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/…
对于较新版本的 mockito(3+?),不需要 mockito-inline 的这种额外依赖。只需要答案 stackoverflow.com/a/40018295/1417088 中的“org.mockito.plugins.MockMaker”文件。
b
bernard paulus

在你的构建文件中添加这个:

如果使用 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.)Byte Buddy 依赖项添加到您的 build.gradle 文件:

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

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


现在应该使用“testImplementation”而不是“testCompile”。 Gradle 不再喜欢“testCompile”了。
很棒的评论,谢谢!编辑为 testImplementation。原评论:testCompile 'org.mockito:mockito-inline:2.13.0'
这会导致在 Linux/OpenJDK 1.8 上运行时出错:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
切换到 Oracle JDK 1.8 时工作正常
这个答案需要更多的选票!绝对是现在最好的方式。
L
Luigi R. Viggiano

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

我所做的是创建一个非最终类来包装最终类并用作委托。 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();
    }
}

缺点是样板代码很多;优点是您可以添加一些可能与您的应用程序业务相关的方法(例如在上述情况下获取用户而不是 accessToken 的 getInstance)。

在您的情况下,我将创建一个委托给最终类的非最终 RainOnTrees 类。或者,如果你可以使它成为非最终版本,那就更好了。


+1。如果需要,您可以使用 Lombok 的 @Delegate 之类的东西来处理大量样板文件。
@luigi 您可以为 Junit 添加代码片段作为示例。我试图为我的最后一堂课创建 Wrapper,但不知道如何测试它。
C
Community

在 Mockito 3 及更多版本中,我遇到了同样的问题并从这个链接修复了它

Mock Final Classes and Methods with Mockito 如下

在 Mockito 可以用于模拟 final 类和方法之前,需要 > 配置它。我们需要在项目的 src/test/resources/mockito-extensions 目录中添加一个名为 org.mockito.plugins.MockMaker 的文本文件,并添加一行文本:mock-maker-inline Mockito 检查扩展目录中的配置文件时它已加载。此文件启用最终方法和类的模拟。


知道如何使它适用于 PowerMock
这是正确的答案,除了 org.mockito.kotlin:mockito-kotlin 之外,您不需要任何其他依赖项
j
jobbert

使用 Powermock。此链接显示如何操作:https://github.com/jayway/powermock/wiki/MockFinal


我认为 PowerMock 就像一种只能在“处方”基础上使用的药物。从某种意义上说:应该很清楚PowerMock有很多问题;并且使用它就像是最后的最后手段;并且应该尽可能避免。
你为什么这么说?
我使用 Powermock 来模拟最终类和静态方法,以增加在 Sonarqube 上正式检查的覆盖率。自 SonarQube 以来,覆盖率为 0%,无论出于何种原因,它都无法识别在其中任何地方使用 Powermock 的类。我和我的团队花了相当长的时间从网上的某个帖子中意识到这一点。所以这只是小心使用 Powermock 并且可能不使用它的原因之一。
现在您可以使用 Mockito 完成所有操作,而无需使用 Power mock 来依赖依赖。
M
Michael_Zhang

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

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

我尝试过各种版本的 mockito-core 和 mockito-all。它们都不起作用。


除此之外,我观察到的一件事是,如果您将 Powermock 与 mockito 一起使用;然后在 'src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker' 中添加 mockmaker 插件文件在模拟最终类中没有用。相反,添加上面 Michael_Zhang 提到的依赖项将解决模拟最终类的问题。另外,请确保您使用的是 Mockito 2 而不是 Mockito1
F
Flávio Faria

我猜您之所以这样做是final,因为您想阻止其他类扩展 RainOnTrees。正如 Effective Java 所建议的(第 15 项),还有另一种方法可以使类关闭以进行扩展,而不使其成为 final

去掉final关键字;将其构造函数设为私有。没有类能够扩展它,因为它不能调用超级构造函数;创建一个静态工厂方法来实例化您的类。 // 这里不再有 final 关键字。公共类 RainOnTrees { public static RainOnTrees newInstance() { return new RainOnTrees(); } private RainOnTrees() { // 私有构造函数。 } public void startRain() { // 这里有一些代码 } }

通过使用此策略,您将能够使用 Mockito 并保持您的类关闭,以便使用少量样板代码进行扩展。


这不适用于使用 mockito 2 也可以模拟的 final 方法。
u
user1945457

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


当然,为什么要模拟一个简单的类?模拟用于“昂贵”的交互:其他服务、引擎、数据类等。
如果您创建了一个实例,那么之后您将无法在其上应用 Mockito.verify 方法。模拟的主要用途是能够测试它的一些方法。
a
andresp

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


O
Ozeetee

为在 Android + Kotlin 上面临相同问题(Mockito + Final Class)的人节省时间。在 Kotlin 中,类默认是 final 的。我在一个带有 Architecture 组件的 Google 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 

这在应用程序级别的 build.gradle 上运行良好,但是我们可以做些什么来在库级别上获得它呢?
你能详细说明一下吗?通常,使用外观模式连接到库。并模拟这些外观类来测试应用程序。通过这种方式,我们不需要模拟任何 lib 类。
我没有看到任何特定于测试的选项。该过程与 official instruction 完全匹配。因此,课程也将在生产中开放。
s
sakis kaliakoudas

试试这个:

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

它对我有用。 “SomeMockableType.class”是您要模拟或监视的父类,而 someInstanceThatIsNotMockableOrSpyable 是您要模拟或监视的实际类。

有关更多详细信息,请查看here


应该注意的是,委托与原生间谍模拟有很大不同。在本机 mockito 间谍中,实例中的“this”引用间谍本身(因为是使用子类)但是,在委托中,“this”将是真实对象 someInstanceThatIsNotMockableOrSpyable。不是间谍。因此,没有办法对自调用函数进行 doReturn/verify。
你能举个例子吗?
C
Cœur

实际上有一种方法,我用它来进行间谍活动。只有满足两个先决条件,它才会为您工作:

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

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

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

k
ksl

如果您使用的是 Mockito2,则可以这样做,它具有支持模拟最终类和方法的新孵化功能。

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

上述两个步骤是激活 mockito 扩展机制并使用此选择加入功能所必需的。

示例类如下:-

最终类.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


您应该在此处包含答案,而不是链接到外部站点。如果过程很长,您可以包括一个概述。
请确保在模拟 @RunWith(PowerMockRunner.class) @PrepareForTest({AFinalClass.class}) 时使用以下注释
@vCillusion - 我展示的示例仅使用 Mockito2 API。使用 Mockito2 的选择加入功能,可以直接模拟最终类,而无需使用 Powermock。
B
BJYC

是的,同样的问题,我们不能用 Mockito 模拟最终课程。准确地说,Mockito 不能模拟/监视以下内容:

最后的课程

匿名类

原始类型

但是在我看来,使用包装类似乎要付出很大的代价,所以请改用 PowerMockito。


S
Serg Burlaka

我认为您需要在原则上进行更多思考。相反,您最终的课程使用他的接口和模拟接口。

为了这:

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

    }

M
Marcin

请查看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();
        }

    }
}

N
Neel

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

尽管 Mockito 在设计上不能模拟最终类,但委托方法是可能的。这有它的优点:

如果这是您的 API 最初的意图,您不必将您的类更改为非最终类(最终类有其好处)。您正在测试围绕您的 API 进行装饰的可能性。

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

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

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


A
Allen

如果您尝试在 test 文件夹下运行单元测试,则最佳解决方案很好。只需按照它添加扩展名即可。

但是,如果您想使用 androidtest 文件夹下的 android 相关 类(如上下文或活动)运行它,the answer 适合您。


H
HandyPawan

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

testImplementation 'org.mockito:mockito-core:2.24.5' testImplementation "org.mockito:mockito-inline:2.24.5"


m
metis

根据这个 GitHub 问题,mockito-android 不支持模拟最终类。您应该为此使用 Mockk。

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


哪个问题?你能提供一个链接吗?
M
Miloš Černilovský

如果您需要在 Android 的插桩测试中使用 Mockito(即在 Android 设备上运行),则不能使用 mockito-inline。有一个特殊的 mockito-android 版本也不能解决“final class”问题。唯一可行的解决方案是 Dexmaker library。唯一的限制是它仅适用于 Android P(Android 9,API 28)及更高版本。可以按如下方式导入:

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

请注意,还有一个“dexmaker-mockito”版本也不适用于最终课程。确保导入“dexmaker-mockito-inline”。


T
Tom N.

正如其他人所说,这不适用于 Mockito。我建议使用反射来设置被测代码正在使用的对象上的特定字段。如果您发现自己经常这样做,您可以将此功能包装在一个库中。

顺便说一句,如果您是最后一个打分班,请停止这样做。我遇到了这个问题,因为我正在使用一个 API,其中所有内容都标记为 final 以防止我对扩展(模拟)的合法需求,我希望开发人员没有假设我永远不需要扩展类。


公共 API 类应该对扩展开放。完全同意。但是,在私有代码库中,final 应该是默认值。
k
kenyee

对我们来说,这是因为我们从 koin-test 中排除了 mockito-inline。一个 gradle 模块实际上需要这个,因此仅在发布版本中失败(IDE 中的调试版本有效):-P


M
Manas Kumar Maharana

对于最终类,在下面添加模拟并调用静态或非静态。

1-将其添加到类级别 @SuppressStatucInitializationFor(value ={class name with package}) 2- PowerMockito.mockStatic(classname.class) 将模拟类 3-然后在调用此类的方法时使用您的 when 语句返回模拟对象。

享受


r
rogerdpack

我能够克服这个消息:

org.mockito.exceptions.base.MockitoException:无法模拟/监视类 org.slf4j.impl.Log4jLoggerAdapter Mockito 无法模拟/监视,因为:最终或匿名类

由此:log = spy(log);

通过使用它来代替:

log = mock(Logger.class);

然后它工作。

我猜“默认”记录器适配器是最终类的一个实例,所以我不能“窥探”它,但我可以模拟整个事情。去搞清楚...

这可能意味着如果您也有方便的话,您可以将它替换为其他一些“非最终”实例。或简化版本等。FWIW ...


J
Jackie

没有尝试最终,但对于私有,使用反射删除修饰符有效!进一步检查,它不适用于final。


这不是回答所问的问题