Reputation: 1713
I kinda confused on how am I supposed to test this
I have this repository class:
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
}
}
This line calls a datasource method that returns a Deferred<Response>
object and awaits for it
val response = dataSource.fetchAuthenticationFromServerAsync(username, password).await()
When I try to test it I always get a NullPointerException
exactly on this line
This is my Test class:
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)
}
}
}
The log output is:
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)
All my other functions on this repository class is suspended functions and all uses withContext(Dispatchers.IO)
and I am able to test them all(any of those calls another class).
This is the only one that's giving me this error.
Thanks in advance for any insight on this.
Upvotes: 3
Views: 3225
Reputation: 665
val deferred: Deferred = mock()
@Before
fun setup() {
doNothing().whenever(deferred.await())
whenever(dataSource.fetchAuthenticationFromServerAsync()).doReturn(deferred)
repository = AuthenticationBox(
dataMapper = dataMapperMock,
dataSource = dataSourceMock
)
}
Then in your test you can do things like:
@Test
fun testFromServer() {
val username = "username"
val password = "password"
runBlocking {
repository.fetchAuthenticationFromServer(username, password)
}
verify(dataSource).fetchAuthenticationFromServerAsync() // Verify fun called
}
You have to mock almost all the mocks behaviour. If I'm honest, I don't know if this is the intended behaviour of the mocks or is a bug in kotlin that does not automatically mock the internal functions.
Upvotes: 0
Reputation: 344
There might be an error with your mock(). Are you sure the datasource mock has an implementation that returns a deferred for the method?
.fetchAuthenticationFromServerAsync(username, password)
This medium post has an example with a mocked method returning a deferred which would give something like this for your use case:
//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()
}
now you should be able to safely call:
mockedDatasource.fetchAuthenticationFromServerAsync().await()
Upvotes: 1