如何用mockito模拟最后一堂课

问题描述 投票:140回答:21

我有一个最后的课,类似这样:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

我在其他类中使用此类,如下所示:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

在我的QUnxswpoi的JUnit测试类中,我想模拟Seasons.java类。我怎么能和Mockito一起做这件事?

java junit mockito
21个回答
96
投票

RainOnTrees

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

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

使用来自testImplementation 'org.mockito:mockito-inline:2.13.0' 的Mockito v1是不可能的:

Mockito有什么限制

  • 需要java 1.5+
  • 不能模拟最后的课程

...


4
投票

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


4
投票

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

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

请回忆一下here的第16项。您可以创建一个包装器(而不是final)并将所有调用转发给final类的实例:

Effective Java

现在你不仅可以嘲笑你的最后一堂课而且还可以监视它:

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

2
投票

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

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

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


2
投票

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

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

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

样本类如下: -

final class.Java

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

}

foo.Java

public final class FinalClass {

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

}

foo test.Java

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


2
投票

Android + Kotlin面临同样问题(Mockito + Final Class)的人节省时间。与Kotlin一样,默认情况下,类是最终的。我在一个带有架构组件的Google Android示例中找到了一个解决方案。解决方案从这里挑选:mocking-the-unmockable

创建以下注释:

https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

修改您的gradle文件。从这里举个例子:/** * 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

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'
}

1
投票

请看看@OpenForTesting class RepoRepository 。它有大量的文档和很多例子。在这里你有一个问题的示例解决方案(为了简化我已经添加了构造函数到JMockit来注入模拟的Seasons实例):

RainOnTrees

1
投票

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

虽然Mockito不能通过设计模拟最终课程,但代表方法是可行的。这有其优点:

  1. 如果这是你的API首先想要的(最终的类有他们的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(); } } } ),你不会被迫将你的类改为非final。
  2. 您正在测试围绕API的benefits的可能性。

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

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

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


1
投票

我认为你原则上需要更多思考。相反,最后一个类使用他的界面和模拟界面。

为了这:

decoration

 public class RainOnTrees{

   fun startRain():Observable<Boolean>{

        // some code here
   }
}

并模拟你的界面:

interface iRainOnTrees{
  public void startRain():Observable<Boolean>
}

0
投票

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

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


0
投票

如果您尝试在测试文件夹下运行单元测试,那么最佳解决方案就可以了。只需按照它添加扩展名即可。

但是如果你想用android相关的类来运行它,比如在androidtest文件夹下的上下文或活动,那么 @Before fun setUp() { rainService= Mockito.mock(iRainOnTrees::class.java) `when`(rainService.startRain()).thenReturn( just(true).delay(3, TimeUnit.SECONDS) ) } 就适合你。


152
投票

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

但就目前而言,这是一个“孵化”功能。它需要一些步骤来激活它,这在Mockito FAQ中有描述:

最终类和方法的模拟是一种孵化,选择加入的功能。它使用Java代理程序检测和子类化的组合,以实现这些类型的可模拟性。由于这与我们当前的机制不同,并且这个机制具有不同的限制,并且由于我们希望收集经验和用户反馈,因此必须明确激活此功能以使其可用;它可以通过mockito扩展机制通过创建包含单行的文件What's New in Mockito 2来完成:

src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

创建此文件后,Mockito将自动使用此新引擎,并且可以执行以下操作:

mock-maker-inline

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


0
投票

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


-5
投票

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


40
投票

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

我所做的是创建一个非final类来包装最终类并用作委托。这方面的一个例子是 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()); 类,这是我的可模拟类:

TwitterFactory

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

在你的情况下,我将创建一个非最终的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(); } } 类,委托给最终的类。或者,如果你能使它成为非最终的,那就更好了。


25
投票

使用Powermock。此链接显示,如何做到:RainOnTrees


22
投票

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

https://github.com/jayway/powermock/wiki/MockFinal

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


15
投票

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

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

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


11
投票

我猜你做了testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9' ,因为你想阻止其他类扩展final。正如RainOnTrees建议的那样(项目15),还有另一种方法可以让一个班级接近延伸而不使它成为Effective Java

  1. 删除final关键字;
  2. 使其构造函数final。没有类可以扩展它,因为它无法调用private构造函数;
  3. 创建一个静态工厂方法来实例化您的类。 super

通过使用此策略,您将能够使用Mockito并使用很少的样板代码关闭扩展类。


7
投票

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


6
投票

尝试一下:

// 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
    }
}

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

有关详细信息,请查看Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

© www.soinside.com 2019 - 2024. All rights reserved.