如何用mockito模拟最后一堂课

时间:2013-01-12 11:23:01

标签: 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();

    }
}

Seasons.java的JUnit测试类中,我想模拟RainOnTrees类。我怎么能用Mockito做到这一点?

28 个答案:

答案 0 :(得分:142)

Mockito 2现在支持最终的类和方法!

但是现在,这是一个"孵化"特征。它需要一些步骤来激活它,如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 concrete = new FinalClass(); 

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

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

在随后的里程碑中,团队将采用编程方式使用此功能。我们将为所有不可挽救的场景确定并提供支持。请继续关注,请告诉我们您对此功能的看法!

答案 1 :(得分:87)

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有什么限制

     
      
  • 需要java 1.5 +

  •   
  • 无法模拟最终课程

  •   
     

...

答案 2 :(得分:37)

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

我所做的是创建一个非final类来包装最终类并用作委托。一个例子是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类。或者,如果你能使它成为非最终的,那就更好了。

答案 3 :(得分:25)

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

答案 4 :(得分:20)

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

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

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

答案 5 :(得分:14)

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

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

我尝试了各种版本的mockito-core和mockito-all。他们都没有工作。

答案 6 :(得分:11)

我猜你做了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
        }
    }
    
  4. 通过使用此策略,您可以使用Mockito并使用少量样板代码关闭扩展类。

答案 7 :(得分:6)

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

答案 8 :(得分:6)

尝试一下:

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

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

有关详细信息,请查看here

答案 9 :(得分:4)

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

答案 10 :(得分:4)

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

  1. 您使用某种DI来注入最终类的实例
  2. Final class实现了一个接口
  3. 请回忆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();
    

答案 11 :(得分:2)

如果您使用的是Mockito2,可以使用新的孵化功能,支持模拟最终类和&amp ;;方法。

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

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

示例类如下: -

FinalClass.java

age = int(raw_input("Age: "))

}

Foo.java

public final class FinalClass {

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

}

FooTest.java

public class Foo {

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

}

希望它有所帮助。

此处有完整的文章mocking-the-unmockable

答案 12 :(得分:2)

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

答案 13 :(得分:2)

这是同样的问题,我们不能用Mockito模拟最后一堂课。准确地说,Mockito不能嘲笑/间谍:

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

但是使用包装类对我来说是一个很大的代价,所以请改为使用PowerMockito。

答案 14 :(得分:1)

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

虽然Mockito 不能,但按照设计,模拟最终类,委托方法可能。这有其优点:

  1. 如果您的API最初是打算使用的,那么您不会被迫将您的课程更改为非最终成绩(最终课程的成绩为benefits)。
  2. 您正在测试围绕API decoration的可能性。
  3. 在您的测试用例中,您故意将调用转发给被测系统。因此,按照设计,你的装饰做任何事情。

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

    更主观的说明: 我更喜欢将框架保持在最低限度,这就是JUnit和Mockito通常对我来说足够的原因。事实上,限制这种方式有时会迫使我重构好。

答案 15 :(得分: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();
        }

    }
}

答案 16 :(得分:1)

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

testImplementation'org.mockito:mockito-core:2.24.5'
testImplementation“ org.mockito:mockito-inline:2.24.5”

答案 17 :(得分:1)

根据此GitHub问题, mockito-android

不支持模拟最终课程。您应该为此使用Mockk。

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

答案 18 :(得分:0)

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

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

答案 19 :(得分:0)

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

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

答案 20 :(得分:0)

对于我们来说,这是因为我们从koin-test中排除了模仿行内联。一个gradle模块实际上需要这个,并且由于原因仅在发行版本(IDE中的调试版本有效)上失败:-P

答案 21 :(得分:0)

我认为您原则上需要多考虑。相反,您的最后一堂课改用他的接口和模拟接口。

为此:

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

    }

答案 22 :(得分:0)

在Mockito 3和更高版本中,我有同样的问题,并已通过此链接解决了该问题

Mock Final Classes and Methods with Mockito 如下

  

在将Mockito用于模拟最终类和方法之前,需要对其进行配置。

     

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

mock-maker-inline
     

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

答案 23 :(得分:0)

对于最终课程,请在下面添加内容以模拟并调用静态或非静态。

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

享受

答案 24 :(得分:0)

我克服了这条消息:

<块引用>

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

  • final 或匿名类

从此:log = spy(log);

改用这个:

log = mock(Logger.class);

然后就可以了。

我猜“默认”记录器适配器是 final 类的一个实例,所以我无法“窥探”它,但我可以模拟整个过程。去图...

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

答案 25 :(得分:0)

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

答案 26 :(得分:-1)

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

<块引用>

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

  1. 请创建类似于 javaTest 的新 kotlin 测试类。
  2. 仅编写您当前在旧 Java 测试用例更改中受到影响的测试。

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

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

以上场景已经通过测试学习得很好。

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

答案 27 :(得分:-5)

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