我还有最后一节课,大概是这样的:
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做到这一点?
你不能用Mockito模拟最后一个类,因为你自己不能这样做。
我所做的是创建一个非final类来包装final类并作为委托使用。一个例子是TwitterFactory类,这是我的mockable类:
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接受用户而不是accessToken)。
在你的例子中,我会创建一个非最终的RainOnTrees类,它委托给最终的类。或者,如果你能让它变成非最终结果,那就更好了。
由RC和Luigi R. Viggiano共同提供的解决方案可能是最好的主意。
尽管Mockito在设计上不能模拟final类,但是委托方法是可能的。这有它的优点:
如果API一开始就打算将类改为非final类(final类有其好处),那么不必强制将类改为非final类。
您正在测试API周围装饰的可能性。
在您的测试用例中,您故意将调用转发到被测试的系统。因此,通过设计,你的装饰什么都不做。
因此,测试还可以证明用户只能修饰API,而不能扩展API。
从更主观的角度来看:
我更喜欢将框架保持在最低限度,这就是为什么JUnit和Mockito通常对我来说就足够了。事实上,以这种方式限制有时也会迫使我进行重构。
实际上有一种方法,我用来监视的。只有满足两个前提条件,它才会为你工作:
使用某种DI注入final类的实例
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();
Mockito 2现在支持final类和方法!
但就目前而言,这只是一个“孵化”功能。它需要一些步骤来激活它,这些步骤在Mockito 2中的新内容中描述:
Mocking of final classes and methods is an incubating, opt-in feature. It uses a combination of Java agent instrumentation and subclassing in order to enable mockability of these types. As this works differently to our current mechanism and this one has different limitations and as we want to gather experience and user feedback, this feature had to be explicitly activated to be available ; it can be done via the mockito extension mechanism by creating the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing a single line:
mock-maker-inline
After you created this file, Mockito will automatically use this new engine and one can do :
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());
In subsequent milestones, the team will bring a programmatic way of using this feature. We will identify and provide support for all unmockable scenarios. Stay tuned and please let us know what you think of this feature!
如果你正在使用Mockito2,这可以做到,新的孵化功能支持模拟最终类和方法。
需要注意的要点:
1. 创建一个名为“org.mockito.plugins”的简单文件。并将其放在名为mockito-extensions的文件夹中。这个文件夹应该在类路径上可用。
2. 上面创建的文件内容应该是一行,如下所示:
mock-maker-inline
为了激活mockito扩展机制并使用此选择加入特性,需要执行上述两个步骤。
示例类如下:-
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));
}
}
希望能有所帮助。
完整的文章呈现在这里嘲笑不可嘲笑的。
在构建文件中添加以下内容:
如果使用gradle: build.gradle
testImplementation 'org.mockito:mockito-inline:2.13.0'
如果使用maven: pom.xml
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>2.13.0</version>
<scope>test</scope>
</dependency>
这是一个使mockito与final类一起工作的配置
如果您面临无法初始化内联字节伙伴模拟生成器。(Android不支持这个模拟生成器。)
将字节伙伴依赖项添加到构建中。gradle文件:
testImplementation 'net.bytebuddy:byte-buddy-agent:1.10.19'
src: https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy
为那些在Android + Kotlin上面临相同问题(Mockito + Final Class)的人节省时间。在Kotlin中,默认情况下类是final的。我在谷歌的一个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
我认为原则上你需要多想想。相反,你最终类使用他的接口和模拟接口。
:
public class RainOnTrees{
fun startRain():Observable<Boolean>{
// some code here
}
}
add
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)
)
}
我正在编写我在Java 11中模拟final/private类及其方法的各种失败尝试之后所遵循的步骤,这些尝试最终对我有用。
在里面创建一个名为org.mockito.plugins.MockMaker的文件
您的test/resources/mockito-extensions文件夹。请创建
Mockito-extensions文件夹(如果还没有)。
在上面的org.mockito.plugins.MockMaker文件中添加一行mock-maker-inline内容
添加
@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.management.*", "jdk.internal.reflect.*", "com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*"})
@PrepareForTest(Utility.class)
类级别的注释。
在测试类中设置过程
@Before
public void setup () {
MockitoAnnotations.initMocks(this);
Mockito.mockStatic(ClassToBeMocked.class);
}
使用Mockito.when(..).thenReturn(..)进行断言
在多个测试用例的情况下,添加下面的代码
@After
public void after() {
Mockito.framework().clearInlineMocks();
}
我正在使用的模拟版本:3.9.0
Java版本:11