315

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

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

4

28 回答 28

259

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

但就目前而言,这是一个“孵化”功能。它需要一些步骤来激活它,这些步骤在 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 concrete = new FinalClass(); 

 FinalClass mock = mock(FinalClass.class);
 given(mock.finalMethod()).willReturn("not anymore");

 assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

在随后的里程碑中,团队将带来使用此功能的程序化方式。我们将为所有不可模拟的场景识别并提供支持。请继续关注,请让我们知道您对此功能的看法!

于 2016-10-13T10:19:39.907 回答
247

只能使用 Mockito v2 模拟最终/静态类/方法。

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

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

Mockito v1 无法做到这一点,来自Mockito FAQ

Mockito 的局限性是什么

  • 需要java 1.5+

  • 无法模拟最终课程

...

于 2013-01-12T11:26:14.870 回答
66

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

  • 如果使用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 与 final 类一起工作的配置

如果您遇到将Byte Buddy依赖项添加到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'

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

于 2018-02-22T08:47:54.983 回答
44

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

于 2013-01-12T11:39:55.103 回答
25

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

于 2013-01-13T20:07:16.233 回答
22

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

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

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

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

mock-maker-inline

Mockito 在加载时会检查扩展目录中的配置文件。此文件启用最终方法和类的模拟。

于 2019-12-28T15:32:53.473 回答
18

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

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

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

于 2017-07-05T22:42:01.353 回答
12

我猜你这样做是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 并保持您的类关闭,以便使用少量样板代码进行扩展。

于 2016-07-14T21:47:26.443 回答
11

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

于 2013-12-10T11:35:22.017 回答
6

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

于 2015-02-02T15:46:39.230 回答
6

为在 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 
于 2018-05-21T22:47:39.497 回答
5

试试这个:

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

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

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

于 2014-11-20T12:17:34.307 回答
5

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

  1. 您使用某种 DI 来注入 final 类的实例
  2. 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();
于 2016-08-24T16:06:24.870 回答
4

如果您使用的是 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

于 2017-02-05T10:22:14.227 回答
3

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

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

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

于 2014-07-18T03:19:19.063 回答
2

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

为了这:

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

    }
于 2019-03-11T09:36:32.620 回答
1

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

    }
}
于 2014-10-31T20:46:41.860 回答
1

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

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

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

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

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

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

于 2015-12-08T21:59:31.480 回答
1

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

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

于 2018-12-05T21:56:42.347 回答
1

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

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

于 2020-04-20T14:54:27.127 回答
1

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

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

于 2020-07-14T13:50:20.617 回答
1

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

于 2021-03-26T17:01:18.040 回答
0

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

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

于 2017-01-05T13:54:49.727 回答
0

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

于 2019-03-05T19:16:14.380 回答
0

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

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

享受

于 2020-01-09T10:04:55.187 回答
0

我能够克服这个消息:

org.mockito.exceptions.base.MockitoException:无法模拟/间谍类 org.slf4j.impl.Log4jLoggerAdapter Mockito 无法模拟/间谍,因为:

  • 最终或匿名类

由此:log = spy(log);

通过使用它来代替:

log = mock(Logger.class);

然后它工作。

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

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

于 2021-01-06T21:34:22.503 回答
-1

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

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

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

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

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

上面的场景已经学习测试好了。

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

于 2021-04-30T06:04:40.290 回答
-5

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

于 2016-05-03T07:49:21.637 回答