我正在为视图模型编写我的第一个单元测试。我指的是GithubBrowserArchitectureComponents示例。我正在测试是否执行为实时数据设置值的函数,是否调用在开关映射中调用的函数,用于存储库类中的实时数据。为此,我正在使用 Mocktio.verify 函数,在这个函数中,我传递一个参数,该参数是存储库类的模拟对象,并验证是否调用了 getPosts 方法。但我发现它实际上不是检查调用,而是调用该方法。而样本中的那个没有
我也在使用 Dagger 2,所以我怀疑存储库是注入而不是被模拟的,因此如示例中所示,我将 testInstrumentationRunner 更改为使用不同应用程序类的自定义版本,即 testApp
@RunWith(JUnit4::class)
class PostViewModelTest {
private val testContext = TestCoroutineContext()
@ExperimentalCoroutinesApi
@get:Rule
val coroutinesDispatcherRule = ViewModelScopeMainDispatcherRule(testContext)
@Rule
@JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private var repository = mock(PostRepository::class.java)
private var appExecutor = mock(AppExecutors::class.java)
private var postDao = mock(PostDao::class.java)
private val postViewModel = PostViewModel(postDao, repository, appExecutor)
@Test
fun fetchWhenObserved(){
postViewModel.showPosts("a", "b")
postViewModel.posts.observeForever(mock())
verify(repository).getPosts("a", "b")
}
}
build.gradle
defaultConfig {
applicationId "com.example.test"
minSdkVersion 16
targetSdkVersion 28
multiDexEnabled true
versionCode 3
versionName "3.0"
testInstrumentationRunner "com.example.test.util.MyTestRunner"
vectorDrawables.useSupportLibrary = true
}
我的测试运行者
/**
* Custom runner to disable dependency injection.
*/
open class MyTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
return super.newApplication(cl, TestApp::class.java.name, context)
}
}
后视图模型
class PostViewModel @Inject
constructor(var postDao: PostDao,
var repository: PostRepository,
var appExecutors: AppExecutors
): ViewModel(){
private val showPosts = MutableLiveData<Pair<String, String>>()
// Get Post Live Data
var posts: LiveData<PagedList<Post>> = Transformations.switchMap(showPosts) { groupIdToUserId ->
repository.getPosts(groupIdToUserId.first, groupIdToUserId.second)
}
fun showPosts(groupId: String, userId: String) {
showPosts.value = groupId to userId
}
}
存储 库
open class PostRepository @Inject constructor(
private var db: AppDatabase,
private var postDao: PostDao,
private var appExecutors: AppExecutors,
private var apiService: ApiService,
private var user: User
) {
fun getPosts(groupId: String, userId: String): LiveData<PagedList<Post>> {
val factory = postDao.allPosts(groupId)
factory.create()
val boundaryCallback = PostBoundaryCallback(groupId, userId, postDao, apiService, appExecutors)
return LivePagedListBuilder(factory, 10)
.setBoundaryCallback(boundaryCallback)
.setFetchExecutor(appExecutors.diskIO())
.build()
}
}
单元测试是测试单个代码单元,一些测试是测试不同类之间的连接。这没关系,但它是集成测试而不是单元测试。
若要测试单元,需要测试输入和输出。例如,方法的输入参数和输出返回值。
因此,对于您的 ViewModel,您只需要测试调用showPosts
方法是否将传递的输入分配给 LiveData。您需要模拟 LiveData 并验证是否已使用您的输入调用该方法。
另外,您应该通过传入输入并验证返回的 LiveData 是否符合您的期望来测试您的存储库。(就我个人而言,我不会从存储库返回 LiveData,我会返回您的值并将其包装在视图模型内的 LiveData 中,但这是您的选择)。
一旦你有了这两个测试,你就测试了这两个单元。如果您随后想要创建集成测试(测试在您的存储库运行其代码并且系统观察结果时 livedata 是否更新),那么这样做的好处就不大了,因为您正在测试系统代码而不是您自己的代码。它也会更混乱(正如你发现的那样)和更脆弱的变化。
希望这能让你朝着正确的方向前进,即使它不是直接的编码答案。 :)