如何使用Mockito模拟最终课程


218

我有最后一堂课,像这样:

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


9
Mockito不允许,但是PowerMock允许。
fge13年

1
从Mockito 2.x开始,Mockito现在支持模拟最终类和方法。
肯特

Answers:



204

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

但是现在这是一个“孵化”功能。它需要一些步骤来激活它,如《 Mockito 2的新增功能》所述

最终课程和方法的模拟是一个孵化器,可以选择加入。它结合使用Java代理工具和子类,以实现这些类型的可模拟性。由于此功能与我们当前的机制不同,并且此机制具有不同的局限性,并且我们希望收集经验和用户反馈,因此必须明确激活此功能才能使用;通过创建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());

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


14
我仍然收到一个错误:无法模拟/间谍类an​​droid.content.ComponentName Mockito无法模拟/间谍,因为:-最终类
IgorGanapolsky

3
确保将org.mockito.plugins.MockMaker文件放在正确的文件夹中。
WindRider

7
即使遵循了上述内容,我也遇到了错误:Mockito无法模拟/监视,因为:-最终类
rcde0

8
@vCillusion这个答案与PowerMock毫无关系。
在线

6
我遵循了这些说明,但是我仍然无法完成这项工作,有人还要做其他事情吗?
佛朗哥

43

您无法使用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();
    }
}

缺点是有很多样板代码。优点是您可以添加一些与您的应用程序业务相关的方法(例如,在上述情况下,getInstance代替用户的访问令牌,而getInstance则接受用户)。

在您的情况下,我将创建一个非最终RainOnTrees类,委托给最终类。或者,如果您可以将其定为非决赛,那会更好。


6
+1。如果需要,可以使用Lombok之类的东西@Delegate来处理很多样板。
ruakh 2014年

2
@luigi您可以为Junit添加代码片段作为示例吗?我尝试为我的最后一堂课创建Wrapper,但无法克服,如何进行测试。
令人难以置信

31

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

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

这是使模仿与最终类一起使用的配置


1
现在应该现在使用“ testImplementation”代替“ testCompile”。Gradle不再喜欢“ testCompile”。
jwehrle

很好的评论,谢谢!编辑为testImplementation。原始评论:testCompile'org.mockito:mockito-inline:
2.13.0'– BennyP

2
在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.)
naXa

切换到Oracle JDK 1.8
naXa,

23

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


30
我认为PowerMock就像其中一种应该仅以“处方”为基础的药物一起使用。从某种意义上说:应该非常清楚PowerMock存在很多问题;并且使用它就像最终的万不得已;并应尽可能避免。
GhostCat

1
你为什么这么说?
PragmaticProgrammer

我使用Powermock模拟最终类和静态方法来增加覆盖范围,这是正式检查的结果Sonarqube。自SonarQube以来,覆盖率是0%,无论出于什么原因,它都无法识别在其中任何地方使用Powermock的类。我花了我和我的团队相当多的时间来从在线某个线程上实现它。因此,这只是对Powermock保持谨慎并且不使用它的原因之一。
阿梅尔

16

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

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

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


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

12

我猜您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
        }
    }

通过使用这种策略,您将能够使用Mockito并保持类关闭以进行扩展,而只需很少的样板代码。


1
这对于最终方法不起作用,最终方法也可以通过模仿2进行模仿。
卢卡斯Rzeszotarski

11

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


2
绝对,为什么要模拟一个简单的类?嘲讽是“昂贵”的相互作用:其它服务,发动机,数据类等
灯条

3
如果创建该实例,则以后无法在其上应用Mockito.verify方法。模拟的主要用途是能够测试其某些方法。
riroo

6

试试看:

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

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

欲了解更多详情,请看这里


3
应该注意的是,委托与本地间谍模拟完全不同。在本机的模仿间谍中,实例中的“ this”引用间谍本身(因为是use子类)。但是,在委托中,“ this”将是someInstanceThatIsNotMockableOrSpyable的真实对象。不是间谍。因此,没有办法对自调用函数进行返回/验证。
丹尼斯C

1
你能举个例子吗?
Vishwa Ratna

5

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


5

实际上,有一种方法可供我监视。只有满足两个先决条件,它才对您有用:

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

请记得有效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();

5

在Mockito 3和更高版本中,我有相同的问题,并已通过此链接修复

使用Mockito模拟最终课程和方法, 如下所示

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

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

mock-maker-inline

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


4

适用于在Android + Kotlin上遇到相同问题(Mockito +最终课程)的人们节省时间。和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上很好用,但是我们应该怎么做才能在库级别上做到这一点?
Sumit T

您能详细说明一下吗?通常,使用外观模式连接到库。并模拟这些外观类以测试应用程序。这样,我们不需要模拟任何lib类。
Ozeetee

3

如果您使用的是Mockito2,可以通过支持孵化最终类和方法的新孵化功能来完成此操作。

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

为了激活模仿扩展机制并使用此选择加入功能,需要执行上述两个步骤。

示例类如下:

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

}

}

希望能帮助到你。

完整的文章在这里嘲笑不可嘲笑


您应在此处提供答案,而不应链接到外部站点。如果过程很长,则可以提供概述。
rghome

请确保在模拟@RunWith(PowerMockRunner.class)@PrepareForTest({AFinalClass.class})
vCillusion

1
@vCillusion-我展示的示例仅使用Mockito2 API,使用Mockito2的选择加入功能,无需使用Powermock即可直接模拟最终类。
ksl

2

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

  • 期末课程
  • 匿名类
  • 基本类型

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


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

    }

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

    }
}

1

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

尽管Mockito 不能通过设计模拟最终类,但可以采用委派方法。这有其优点:

  1. 如果这是您的API最初打算的,那么您不必强迫将类更改为非最终类(最终类具有其优势))。
  2. 您正在测试围绕API 进行装饰的可能性。

在您的测试用例中,您有意将呼叫转发到被测系统。因此,通过设计,您的装饰不会做任何事情。

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

从更主观的角度来看:我更喜欢将框架保持在最低水平,这就是为什么JUnit和Mockito通常足以满足我的需求。实际上,限制这种方式有时也会迫使我也进行良好的重构。


1

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

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


1

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

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


0

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

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


1
公共API类应该开放以进行扩展。完全同意。但是,在私有代码库中,final应将其作为默认值。
ErikE

0

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


0

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

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

请享用


-5

并没有尝试final,但私下使用反射删除了修饰符!经过进一步检查,它不适用于最终版本。


这不能回答所问的问题
Sanjit Kumar Mishra,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.