繁体   English   中英

如何用 mockito 模拟最后一堂课

[英]How to mock a final class with 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 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 v2 可以模拟最终/静态类/方法。

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

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

这在 Mockito v1 中是不可能的,来自Mockito 常见问题解答

Mockito 的局限性是什么

  • 需要 Java 1.5+

  • 无法模拟最终课程

...

将此添加到您的构建文件中:

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

这是使 mockito 与最终类一起工作的配置

如果您遇到Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.) 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

你不能用 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类,该类委托给最终的类。 或者,如果您可以将其设为非最终版本,那就更好了。

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

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

使用 Mockito 模拟最终类和方法如下

在 Mockito 可以用于模拟 final 类和方法之前,需要对其进行 > 配置。

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

 mock-maker-inline

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

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

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

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

我猜你把它做成了final是因为你想防止其他类扩展RainOnTrees 正如Effective 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 } }

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

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

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

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

试试这个:

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

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

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

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

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

请回忆《 Effective 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();

如果您使用的是 Mockito2,则可以使用新的孵化功能来完成,该功能支持模拟最终类和方法。

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

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

示例类如下:-

最终类

public final class FinalClass {

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

}

文件

public class Foo {

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

}

测试文件

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

}

}

希望能帮助到你。

完整的文章呈现在这里嘲弄不可模仿的

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

  • 期末班
  • 匿名类
  • 原始类型

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

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

为了这:

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

    }

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

    }
}

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

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

  1. 如果您的 API 最初打算这样做(最终类有其好处),则您不必被迫将类更改为非最终类。
  2. 您正在测试围绕 API 进行装饰的可能性。

在您的测试用例中,您故意将调用转发到被测系统。 因此,通过设计,您的装修没有做任何事情。

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

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

如果您尝试在test文件夹下运行 unit-test ,最好的解决方案是好的。 只需按照它添加扩展名即可。

但是,如果您想使用androidtest文件夹下的上下文或活动等与android 相关的类来运行它,那么答案就是为您准备的。

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

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

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

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

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

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

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

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

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

享受

我能够克服这个消息:

org.mockito.exceptions.base.MockitoException:无法模拟/监视类 org.slf4j.impl.Log4jLoggerAdapter Mockito 不能模拟/监视,因为:

  • 最终或匿名类

从这里: log = spy(log);

改用这个:

log = mock(Logger.class);

然后它起作用了。

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

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

如果您需要在 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”。

现在是 2021 年,对于所有来到这里寻找 javaTest 更新 kotlin 类问题答案的新人来说。 对于他们的旧代码库。

是时候逐步开始将您的测试类从 Java 迁移到 kotlin。

  1. 请创建类似于 javaTest 的新 kotlin 测试类。
  2. 仅编写您当前正在影响的测试,作为旧 Java 测试用例更改的一部分。

你应该使用 MockK 请参考https://mockk.io/

以上暗示在您将一些新的 kotlin 类插入旧的 Java 实现并旨在平滑更新测试类的情况下。

以上场景已经很好地学习了测试。

注意:我知道这不是答案,但值得分享我在处理遗留代码库时学到的策略。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM