首页 > 解决方案 > 使用 Mockito.verify() 时,不只是检查是否在模拟对象上调用了该函数,而是调用来自真实对象的方法

问题描述

我正在为视图模型编写我的第一个单元测试。我指的是 GithubBrowserArchitectureComponents 示例。我正在测试是否执行为实时数据设置值的函数,是否为来自存储库类的实时数据调用在 switch 映射中调用的函数。为此,我使用了 Mocktio.verify 函数,在这个函数中,我传递了一个参数,该参数是 Repository 类的模拟对象,并验证是否调用了 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")
    }

}

构建.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
    }

我的TestRunner

/**
 * 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()
    }
}

标签: androidgradlekotlinmockito

解决方案


单元测试正在测试单个代码单元,您的一些测试正在测试不同类之间的连接。这没关系,但它是集成测试而不是单元测试。

要测试单元,您需要测试输入和输出。例如方法的输入参数和输出返回值。

因此,对于您的 ViewModel,您只想测试调用您的showPosts方法是否将传递的输入分配给 LiveData。您需要模拟 LiveData 并验证已使用您的输入调用该方法。

另外,您应该通过传入输入并验证返回的 LiveData 是否符合您的期望来测试您的存储库。(我个人不会从存储库返回 LiveData,我会返回您的值并将其包装在视图模型内的 LiveData 中,但这是您的选择)。

一旦你完成了这两个测试,你就已经测试了这两个单元。如果您想创建一个集成测试(测试实时数据在您的存储库运行其代码并且系统观察结果时更新),那么这样做的好处就更少,因为您正在测试系统代码而不是您自己的代码。它也会更混乱(正如你所发现的那样)并且对变化更脆弱。

希望这能让您朝着正确的方向前进,即使它不是直接的编码答案。:)


推荐阅读