Stylianos Gakis
03/13/2025, 4:59 PMregisterTestResponse
etc.
I have a test where I am testing a ViewModel, some action happens, some state is set to being in a "loading" state, so I want to be able to assert that, and then I want to be able to add the response to the ApolloClient, so that it will return that value back to the suspend function which is waiting for it, so that I can also assert what happens after the network has responded.
What happens now instead is that I call the action on my ViweModel, the state is set to be in this pending state, then the suspend function tries to hit the ApolloClient, it does not find the response registered in the test TestNetworkTransport
so it throws with No response registered for operation ...
.
Outside of Apollo, I typically make this work using Turbines, where if someone does an awaitItem()
on it, it will not immediately throw, but it will wait a bit to see if someone will add a response to that turbine, and when it's there then it gives it to the caller who was waiting for it in a suspending manner.
Does what I say here make sense? I can try to explain it a bit better if not ๐Stylianos Gakis
03/13/2025, 5:03 PMprivate val operationsToResponses = mutableMapOf<Operation<out Operation.Data>, TestResponse>()
with a turbine myself, which will wait if the item is not there yet, instead of throwing.
Will try thatStylianos Gakis
03/13/2025, 5:32 PMprivate sealed interface TestResponse {
object NetworkError : TestResponse
class Response(val response: ApolloResponse<out Operation.Data>) : TestResponse
}
@ApolloExperimental
private class TurbineMapTestNetworkTransport : NetworkTransport {
private val lock = reentrantLock()
private val operationsToTurbineResponses: MutableMap<Operation<out Data>, Turbine<TestResponse>> =
mutableMapOf<Operation<out Operation.Data>, Turbine<TestResponse>>()
override fun <D : Operation.Data> execute(request: ApolloRequest<D>): Flow<ApolloResponse<D>> {
return flow {
// "Emulate" a network call
yield()
val response = lock.withLock {
operationsToTurbineResponses.getOrPut(request.operation) {
Turbine<TestResponse>(name = "Turbine for operation ${request.operation.name()}")
}
}.let {
it.awaitItem()
}
val apolloResponse = when (response) {
is TestResponse.NetworkError -> {
ApolloResponse.Builder(operation = request.operation, requestUuid = request.requestUuid)
.exception(exception = ApolloNetworkException("Network error registered in MapTestNetworkTransport"))
.build()
}
is TestResponse.Response -> {
@Suppress("UNCHECKED_CAST")
response.response as ApolloResponse<D>
}
}
emit(apolloResponse.newBuilder().isLast(true).build())
}
}
fun <D : Operation.Data> register(operation: Operation<D>, response: ApolloResponse<D>) {
lock.withLock {
operationsToTurbineResponses.getOrPut(operation) {
Turbine<TestResponse>(name = "Turbine for operation ${operation.name()}")
}
}.also {
it.add(TestResponse.Response(response))
}
}
fun <D : Operation.Data> registerNetworkError(operation: Operation<D>) {
lock.withLock {
operationsToTurbineResponses.getOrPut(operation) {
Turbine<TestResponse>(name = "Turbine for operation ${operation.name()}")
}
}.also {
it.add(TestResponse.NetworkError)
}
}
override fun dispose() {}
}
@ApolloExperimental
fun <D : Operation.Data> ApolloClient.registerSuspendingTestResponse(
operation: Operation<D>,
response: ApolloResponse<D>,
): Unit = (networkTransport as? TurbineMapTestNetworkTransport)?.register(operation, response)
?: error("Apollo: ApolloClient.registerSuspendingTestResponse() can be used only with TurbineMapTestNetworkTransport")
@ApolloExperimental
fun <D : Operation.Data> ApolloClient.registerSuspendingTestResponse(
operation: Operation<D>,
data: D? = null,
errors: List<Error>? = null,
) = registerSuspendingTestResponse(
operation,
ApolloResponse.Builder(
operation = operation,
requestUuid = uuid4(),
)
.data(data)
.errors(errors)
.build(),
)
@ApolloExperimental
fun <D : Operation.Data> ApolloClient.registerSuspendingTestNetworkError(operation: Operation<D>): Unit =
(networkTransport as? TurbineMapTestNetworkTransport)?.registerNetworkError(operation)
?: error("Apollo: ApolloClient.registerSuspendingTestNetworkError() can be used only with TurbineMapTestNetworkTransport")
Just changing the map to be a map of turbines seems to be promising so far actually!mbonnin
03/13/2025, 5:55 PMmbonnin
03/13/2025, 5:55 PMChannel
too?Stylianos Gakis
03/13/2025, 5:58 PMStylianos Gakis
03/13/2025, 8:37 PMmbonnin
03/13/2025, 9:04 PMmbonnin
03/13/2025, 9:04 PMmbonnin
03/13/2025, 9:05 PMmbonnin
03/13/2025, 9:06 PMStylianos Gakis
03/13/2025, 9:07 PMmbonnin
03/13/2025, 9:11 PMmbonnin
03/13/2025, 9:12 PMmbonnin
03/13/2025, 9:13 PMStylianos Gakis
03/13/2025, 9:15 PMmbonnin
03/13/2025, 9:17 PMmbonnin
03/13/2025, 9:18 PMStylianos Gakis
03/14/2025, 9:32 AM