使用Mockito.verify()时,不只是检查是否在模拟对象上调用了该函数,还从真实对象中调用了方法

时间:2019-06-07 13:49:26

标签: android gradle kotlin mockito

我正在为视图模型编写我的第一个单元测试。我指的是GithubBrowserArchitectureComponents示例。我正在测试是否执行为实时数据设置值的函数,是否从存储库类为该实时数据调用在切换映射中调用的函数。为此,我使用了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")
    }

}

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
    }

MyTestRunner

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

PostViewModel

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

1 个答案:

答案 0 :(得分:0)

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

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

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

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

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

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