事情是从一个 Unit Test开始的。
我写了一个 object 的类,里边有简单的几个方法,然后开始用 Mocito 写一个 Unit Test 进行测试,方便起见,我抽象一下如下:
object A {
fun hello(): String {
println("Hello")
return "hello"
}
fun world(): String {
println("World!")
return "World"
}
}
复制代码
最终发现问题其实是在于 object 类并不是静态类,object 中定义的方法也并不是 static 的方法,这个可以通过编译后的 class file 看出来:
// access flags 0x2
private <init>()V
L0
LINENUMBER 14 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LA; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x19
public final static LA; INSTANCE
@Lorg/jetbrains/annotations/NotNull;() // invisible
复制代码
object 本身就是一个构造函数为 private 的普通类,同时提供了一个 public 的 INSTANCE,就是通常我们说的单例对象。
上面的发现可能是大多数人比较清楚的 kotlin 的一个特点,因为我们偶尔也会在 java 中调 A,就是通过 A.INSTANCE.xxx
但我忽略的是 A 中的方法其实也并不是 static 的,而是普通的 final 方法:
// access flags 0x11
public final hello()Ljava/lang/String;
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 16 L0
LDC "Hello"
ASTORE 1
L1
复制代码
因此无法直接使用 mockStatic 来对 hello() 进行mock,因为它本质上并不是 static 的。
想要解决这个问题也比较简单,为 hello() 添加 @JvmStatic 即可,可以看到加上这个注解之后,byecode 相对应增加了一个 static :
// access flags 0x19
public final static hello()Ljava/lang/String;
@Lkotlin/jvm/JvmStatic;()
@Lorg/jetbrains/annotations/NotNull;() // invisible
L0
LINENUMBER 20 L0
LDC "Hello"
ASTORE 0
L1
复制代码
这种情况下再使用 mockito 的 mockStatic 就没有问题了。
但是到这里我又有一个问题了,为什么 kotlin 的 object class 不默认将方法作为 static 的呢?并且 kotlin 中也没有 static 的关键字,这种语言设计的原因又是什么呢?
个人认为是由于 kotlin 设计之初的一个原则就是:一切皆是对象。在 Java 中 static 的变量与 object 有很大的区别,这意味着你没有办法去基于此实现接口,或者是将 static 的变量作为参数传给函数。