3

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.

1
  • 1
    It would be a lot answer to help you debug this problem if you also included (simplified) versions of your AuthenticationDataMapper, AuthenticationDataSource, AuthenticationRepository, BaseRepository, ResourceResult, UserAuthentication and any other class I might have forgotten to ask you about. Also, what mock library and version of it do you use? Commented Oct 24, 2019 at 18:09

2 Answers 2

1

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()
Sign up to request clarification or add additional context in comments.

Comments

0
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.