带有模拟和协程的Android测试存储库返回NullPointerException

时间:2019-10-10 17:54:19

标签: android mockito kotlin-coroutines mockito-kotlin

我有点困惑应该如何测试

我有这个存储库类:


class AuthenticationBox(
    private val dataMapper: AuthenticationDataMapper,
    private val dataSource: AuthenticationDataSource
) : BaseRepository<UserAuthentication>(), AuthenticationRepository {

override suspend fun fetchAuthenticationFromServer(
        username: String,
        password: String
    ): ResourceResult<UserAuthentication>? {
        var result: ResourceResult<UserAuthentication>? = null

        withContext(Dispatchers.IO) {
            try {
                val response = dataSource
                    .fetchAuthenticationFromServerAsync(username, password).await()

                if (response.hasErrors()) {
                    result =
                        ResourceResult.Error(ApolloException(response.errors()[0].message()))

                } else {
                    response.data()?.let {
                        val authorization = dataMapper.toDomain(it.loginResponse)

                        result = ResourceResult.Success(authorization)
                    }

                }

            } catch (error: ApolloException) {
                result = handleAuthenticationError(error)
            }
        }
        return result

    }


}

此行调用一个数据源方法,该方法返回一个Deferred<Response>对象并等待它 val response = dataSource.fetchAuthenticationFromServerAsync(username, password).await()

当我尝试对其进行测试时,总是总是在此行上得到NullPointerException
这是我的测试班:

class AuthenticationBoxTest {

    lateinit var repository: AuthenticationRepository

    var dataSourceMock: AuthenticationDataSource = mock()
    var dataMapperMock: AuthenticationDataMapper = mock()


   @Before
    fun setup() {
        repository = AuthenticationBox(
            dataMapper = dataMapperMock,
            dataSource = dataSourceMock
        )
    }

     @Test
    fun testFromServer() {
        val username = "username"
        val password = "password"
        runBlocking {
            repository.fetchAuthenticationFromServer(username, password)
        }
    }

}

日志输出为:

java.lang.NullPointerException
    at br.com.ampli.authentication.repository.AuthenticationBox$fetchAuthenticationFromServer$2.invokeSuspend(AuthenticationBox.kt:45)
    at |b|b|b(Coroutine boundary.|b(|b)
    at br.com.ampli.authentication.repository.AuthenticationBox.fetchAuthenticationFromServer(AuthenticationBox.kt:42)
    at br.com.ampli.authentication.repository.AuthenticationBoxTest$testFromServer$1.invokeSuspend(AuthenticationBoxTest.kt:126)
Caused by: java.lang.NullPointerException
    at br.com.ampli.authentication.repository.AuthenticationBox$fetchAuthenticationFromServer$2.invokeSuspend(AuthenticationBox.kt:45)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)


我在此存储库类上的所有其他函数都是已暂停的函数,并且全部使用withContext(Dispatchers.IO),并且我能够测试所有这些函数(这些函数均调用另一个类)。 这是唯一给我这个错误的人。

在此先感谢您的任何见识。

2 个答案:

答案 0 :(得分:0)

mock()可能有错误。您确定数据源模拟具有实现返回方法延迟的实现吗?

.fetchAuthenticationFromServerAsync(username, password)

This medium post的示例包含一个模拟方法,该方法返回一个延迟值,这将为您的用例提供类似的信息:

//a small helper function first
fun <T> T.toDeferred() = GlobalScope.async { this@toDeferred }

val authResult = dummyAuthDataSourceResult  //dummy result
val mockedDatasource = mock<AuthenticationDataSource> {    
      on { fetchAuthenticationFromServerAsync() } doReturn authResult.toDeferred()
}

现在您应该可以安全地致电:

mockedDatasource.fetchAuthenticationFromServerAsync().await()

答案 1 :(得分:0)

val deferred: Deferred = mock()

@Before
fun setup() {
    doNothing().whenever(deferred.await())
    whenever(dataSource.fetchAuthenticationFromServerAsync()).doReturn(deferred)

    repository = AuthenticationBox(
        dataMapper = dataMapperMock,
        dataSource = dataSourceMock
    )
}

然后在测试中,您可以执行以下操作:

@Test
fun testFromServer() {
    val username = "username"
    val password = "password"
    runBlocking {
        repository.fetchAuthenticationFromServer(username, password)
    }
    verify(dataSource).fetchAuthenticationFromServerAsync() // Verify fun called
}

您必须模拟几乎所有模拟行为。老实说,我不知道这是模拟的预期行为,还是kotlin中的错误不会自动模拟内部功能。