Robolectric接口测试
-
Robolectric的集成在上一章节中已经提到,如何集成Robolectric进行单元测试请看点击下面链接:https://www.jianshu.com/p/7c215ab51e28
-
该章节用了LiveData实现模拟接口拉取。
LiveData与ViewModel
-
LiveData是google今年推出的一种新的观察者数据模型。大概意思就是当前ViewModel依赖的activity/fragment不可见时候,LiveData将处于睡眠状态,不可接受数据更新通知;当activity/fragment可见时候,LiveData恢复活跃状态,可接受数据更新通知。当activity/fragment销毁后,liveData也会销毁。
-
ViewModel与LiveData是对情侣,缺少了谁生活都不会美满。ViewModel的实例化是通过ViewModelProviders来创建的,下面举个实例化ViewModel的例子
//MyViewModel就是继承ViewModel的Class类
val viewModel = ViewModelProviders.of(activity, factory).get(
MyViewModel::class.java
)
LiveData二次封装
-
首先LiveData的子类MutableLiveData<Object>可以接受任意类型作为参数。定义一个类型用作接口请求类型,分别定义加载,失败,成功三种状态。
-
定义接口返回信息
//子定义枚举状态
enum class CallBackState {
LOADING, SUCCESS, FAILURE
}
//封装接口
class DataCallBack<out T> constructor(
val data: T?,
val callBackState: CallBackState,
val msg: MyMessage? = null
) {
companion object {
@JvmStatic
fun <T> loading(data: T? = null, message: MyMessage? = null): DataCallBack<T> {
return DataCallBack(data, CallBackState.LOADING, message)
}
@JvmOverloads
@JvmStatic
fun <T> failure(data: T? = null, failureMsg: MyMessage? = null): DataCallBack<T> {
return DataCallBack(data, CallBackState.FAILURE, failureMsg)
}
@JvmOverloads
@JvmStatic
fun <T> success(data: T? = null, message: MyMessage? = null): DataCallBack<T> {
return DataCallBack(data, CallBackState.FAILURE, message)
}
}
}
//自定义接口返回信息(这一步其实多余的)
class MyMessage constructor(
val msg: String,
val failure: Throwable? = null
)
//重命名,代码看上去美观,在代码执行上无差异
typealias CallBackLiveData<T> = MutableLiveData<DataCallBack<T>>
Robolectric开测
模拟接口返回代码
- 首先创建ActivityA,创建正确的接口回调代码,等待单元测试。这里我就简单的模拟下,接口拉取后返回请求数据状态。
class MainActivity : BaseActivity() {
private var mViewModel: MyViewModel? = null
override fun getLayoutId(): Int {
return R.layout.activity_main
}
override fun init() {
//init,自己封装的方法,其实就是onCreate方法后执行
mViewModel = MyViewModel.getViewModel(this, null)
mViewModel?.myInfo?.observe(this, object : Observer<DataCallBack<Info>> {
override fun onChanged(t: DataCallBack<Info>?) {
when (t?.callBackState) {
//t?.data 获取接口返回的数据结构
CallBackState.FAILURE -> {
showToast(t.msg?.msg ?: "")
}
CallBackState.SUCCESS -> {
showToast("加载成功")
}
CallBackState.LOADING -> {
showToast("加载中")
}
}
}
})
}
}
Robolectric单元测试
- 首先了解下单元测试的注解:@Before,单元测试方法执行前,该注解修饰的方法先执行。@After,单元测试方法执行结束后,再执行该注解修饰的方法。
- 造假接口返回的Json数据,其中假如两个数据结构的key相同,则认为是非法数据。
const val info = """{
"items": [{
"name": "magic",
"phone": "2123",
"key": 1
},{
"name": "magic",
"phone": "2123",
"key": 2
},{
"name": "magic",
"phone": "2123",
"key": 3
},{
"name": "magic",
"phone": "2123",
"key": 2
}]
}"""
- 创建一个模拟接口请求的方法,用@Before修饰,该方法的作用是解析Json数据,返回解析后的数据结构。此方法用于模拟接口请求。
@Before
fun buildData() {
data = JSON.parseObject(info, Info::class.java)
}
- 创建单元测试方法,用@Test修饰该方法。下面主要是通过
@Test
fun isSuccessShowToast() {
//让activity走正常生命周期
val activity = Robolectric.setupActivity(MainActivity::class.java)
myViewModel = MyViewModel.getViewModel(activity, null)!!
//isInvalidId方法里面的实现就是用了map数据,key是数据结构的key,value就是数据结构本身。如果put的时候发现已经存在相同的key就返回false;如果能遍历数据源完成put操作,则返回true。
val result = myViewModel.isInvalidId(data?.items)
if (result) {
val msg = MyMessage("加载成功", null)
myViewModel.myInfo.value = DataCallBack(data, CallBackState.SUCCESS, msg)
} else {
val msg = MyMessage("加载失败", Throwable(message = "服务器出错", cause = null))
myViewModel.myInfo.value = DataCallBack(data, CallBackState.FAILURE, msg)
}
//ShadowToast是Robolectric获取Activity的Toast提供的方法体
//ShadowToast.getTextOfLatestToast()意思就是获取activity当前最新的toast
Assert.assertEquals("加载成功", ShadowToast.getTextOfLatestToast())
}
-
运行结果
5.1 把上面的info字符串key都改成不相同的整数时,这时候运行单元测试就能运行通过。
image.png
5.2 把上面的info字符串key都改成相同的整数时,这时候运行单元测试就必定会失败,而且还会给出准确的报错信息。
image.png
学习总结
- Robolectric其实真的比较方便的,你看这里我需要给自己功能性代码进行单元测试,匹配Toast的提示是否正确。而Robolectric则提供各种方便快捷的API让你拿到各种控件的状态,实属好用。
- Robolectric还有更多好用的API,欢迎大家自己去官网文档查询。
附带完整的单元测试代码
package cn.magic.test
import cn.magic.test.bean.Info
import cn.magic.test.callback.CallBackState
import cn.magic.test.callback.DataCallBack
import cn.magic.test.callback.MyMessage
import cn.magic.test.ui.MainActivity
import com.alibaba.fastjson.JSON
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowToast
@RunWith(RobolectricTestRunner::class)
@Config(
application = android.app.Application::class,
sdk = [21]
)
class MyTestDataTest {
lateinit var myViewModel: MyViewModel
var data: Info? = null
@Before
fun buildData() {
data = JSON.parseObject(info, Info::class.java)
}
@Test
fun isSuccessShowToast() {
//让activity走正常生命周期
val activity = Robolectric.setupActivity(MainActivity::class.java)
myViewModel = MyViewModel.getViewModel(activity, null)!!
val result = myViewModel.isInvalidId(data?.items)
if (result) {
val msg = MyMessage("加载成功", null)
myViewModel.myInfo.value = DataCallBack(data, CallBackState.SUCCESS, msg)
} else {
val msg = MyMessage("加载失败", Throwable(message = "服务器出错", cause = null))
myViewModel.myInfo.value = DataCallBack(data, CallBackState.FAILURE, msg)
}
Assert.assertEquals("加载成功", ShadowToast.getTextOfLatestToast())
}
}
const val info = """{
"items": [{
"name": "magic",
"phone": "2123",
"key": 1
},{
"name": "magic",
"phone": "2123",
"key": 2
},{
"name": "magic",
"phone": "2123",
"key": 3
},{
"name": "magic",
"phone": "2123",
"key": 2
}]
}"""
网友评论