版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_32768743/article/details/88360766
kotlin提倡尽量用val
,只是在Spring Boot中一使用val
就经常出现一些问题
mock数据就是其中的一个
比如这样一段代码
import com.github.jsonzou.jmockdata.JMockData
data class Demo(
val id: Int,
val name: String
)
fun main() {
val mock = JMockData.mock(Demo::class.java)
}
运行就会报错
Exception in thread "main" com.github.jsonzou.jmockdata.MockException: java.lang.InstantiationException: Demo
at com.github.jsonzou.jmockdata.mocker.BeanMocker.mock(BeanMocker.java:63)
at com.github.jsonzou.jmockdata.mocker.ClassMocker.mock(ClassMocker.java:38)
at com.github.jsonzou.jmockdata.mocker.BaseMocker.mock(BaseMocker.java:35)
at com.github.jsonzou.jmockdata.JMockData.mock(JMockData.java:33)
at com.github.jsonzou.jmockdata.JMockData.mock(JMockData.java:21)
at DemoKt.main(demo.kt:9)
at DemoKt.main(demo.kt)
Caused by: java.lang.InstantiationException: Demo
at java.lang.Class.newInstance(Class.java:427)
at com.github.jsonzou.jmockdata.mocker.BeanMocker.mock(BeanMocker.java:35)
... 6 more
Caused by: java.lang.NoSuchMethodException: Demo.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 7 more
如果加上NoArg编译插件
如下
@NoArg data class Demo(val id: Int, val name: String)
fun main() {
val mock = JMockData.mock(Demo::class.java)
println("mock: $mock")
}
输出为
JMock数据没有写入,因为用了val
如果改为var
但是,这样似乎违反了不可变的本意
使用mockk倒是可以使用val
,没有问题
只是这个mock结果,不是我希望的
kotlin的class有一个主构造函数,如果能拿到主构造函数的每个参数和每个参数的类型,再交由JMockData mock数据,最后调用主构造函数,完成mock,是不是能达成曲线救国?
基于上述想法,编写下面的代码,完成mock
import com.github.jsonzou.jmockdata.JMockData
import com.sumixer.ys.api.annotation.NoArg
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
@NoArg data class Demo(val id: Int, val name: String)
fun main() {
val kClass = Demo::class
// 获取主构造函数
val primaryConstructor = kClass.primaryConstructor?:throw RuntimeException("无主构造函数:$kClass")
println(primaryConstructor)
// 获取主构造函数的参数和类型
val parameters = primaryConstructor.parameters
println(parameters)
parameters.forEach {
println("${it.name}: ${it.type}")
}
// 根据主构造函数的参数和类型mock数据
val map = HashMap<KParameter, Any>()
parameters.forEach {
// 根据全限定类型名获取对应的类的类型
// 注意:kotlin.xxx 无法获取成功,需要映射到java类型
val clazz = Class.forName(map2java(it.type.toString()))
val mock = JMockData.mock(clazz)
map[it] = mock
}
// 调用主构造函数完成mock
val demo = primaryConstructor.callBy(map)
println("mock demo: $demo")
}
fun map2java(name: String): String {
return when(name) {
"kotlin.Int" -> "java.lang.Integer"
"kotlin.String" -> "java.lang.String"
else -> ""
}
}
上面的代码需要注意的地方
- kClass.primaryConstructor 返回为可空,难道有class没有主构造函数?
- Class.forName 没法成功获取
kotlin.xxx
类型,需要手动映射 - 速度比较慢(我的主观感受)
做到这里,离实际使用还是有一些距离的,现在最注意的问题是需要把泛型加上
接着写
import com.github.jsonzou.jmockdata.JMockData
import com.sumixer.ys.api.annotation.NoArg
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
@NoArg data class Demo(val id: Int, val name: String)
fun main() {
val demo = kmockdata<Demo>()
println("mock demo: $demo")
}
inline fun <reified T : Any> kmockdata() :T{
val kClass = T::class
// 获取主构造函数
val primaryConstructor = kClass.primaryConstructor?:throw RuntimeException("无主构造函数:$kClass")
println(primaryConstructor)
// 获取主构造函数的参数和类型
val parameters = primaryConstructor.parameters
println(parameters)
parameters.forEach {
println("${it.name}: ${it.type}")
}
// 根据主构造函数的参数和类型mock数据
val map = HashMap<KParameter, Any>()
parameters.forEach {
// 根据全限定类型名获取对应的类的类型
// 注意:kotlin.xxx 无法获取成功,需要映射到java类型
val clazz = Class.forName(map2java(it.type.toString()))
val mock = JMockData.mock(clazz)
map[it] = mock
}
// 调用主构造函数完成mock
val mock = primaryConstructor.callBy(map)
return mock
}
fun map2java(name: String): String {
return when(name) {
"kotlin.Int" -> "java.lang.Integer"
"kotlin.String" -> "java.lang.String"
else -> ""
}
}
写泛型,我是蒙圈的
得益于IDEA强大的代码提示功能和自动修复功能,完成泛型的部分
难点
- T怎么获取KClass
其实没什么,打两个冒号,IDEA就自动提示了 - 不是Any的子类,没有primaryConstructor属性
这个也是靠IDEA的自动修复功能完成的
最后,在我的实际项目中使用,发现无法获取集合泛型的类型,如
List<Goods>
看来接下来的重点,还是处理泛型