Stylianos Gakis
10/24/2023, 3:15 PMinterface FooAction {
suspend fun doSomething(egg: Egg): Omelette
}
class TestFooAction : FooAction {
val eggTurbine: Turbine<Egg>
val omeletteTurbine: Turbine<Omelette>
override suspend fun doSomething(egg: Egg): Omelette {
eggTurbine.add(egg)
return omeletteTurbine.awaitItem()
}
}
@Test
fun `asd`() = runTest {
val testFooAction = TestFooAction()
val service = Service(fooAction = testFooAction)
...
service.doThing()
testFooAction.eggTurbine.awaitItem().assert is the correct input
assertIsLoadingOrSomething()
testFooAction.omeletteTurbine.add(someSpecificOmelette)
assertStateIsCorrectAfterFooActionCompleted()
}
And it works super well for my tests.
One thing which has been a tiny bit tricky is just the mental overhead regarding the “input” and the “output” turbines in the test double, which I never seem to name intuitively enough for the API of the test class to be simple to use without having to look inside of it to see what the turbines actually are.
Have you found any common naming conventions for those turbines that have worked for you? Something like
val <typeName>InputTurbine: Turbine<TypeName>()
val <typeNameOutputTurbine: Turbine<TypeName>()
or something else? Maybe someone who’s using this style for test doubles in a sufficiently big team (where people have had many opportunities to be confused by this 😄) can share their thoughts here?Benoit Quenaudon
11/03/2023, 3:05 PMeggTurbine.add(egg)
is meaningfull. Just kill it.val getPreSignInDataRequests = channels.create<GetPreSignInDataRequest>()
val getPreSignInDataResponses = Turbine<ApiResult<GetPreSignInDataResponse>>()
override suspend fun getPreSignInData(request: GetPreSignInDataRequest): ApiResult<GetPreSignInDataResponse> {
getPreSignInDataRequests.add(request)
return getPreSignInDataResponses.awaitItem()
}
Stylianos Gakis
11/03/2023, 3:11 PMI don’t think doing the step where you doYeah, most of my test doubles do not have this “confirmation” turbine in them actually. But sometimes I do in fact want to make sure that my presenter has handled the new event well, and has responded to some service with the right input to it. Hmm from your snippet here, how different is youris meaningfull. Just kill it.eggTurbine.add(egg)
getPreSignInDataRequests
? Also not quite sure what channels.create<GetPreSignInDataRequest>()
does in your case, but how different is it from what I was doing with the turbine? We are using that turbine as a channel to hold those requests more or less too.
Or did you mean by this that in those places which do in fact need this pair of channels, that the way you’d name it is fooRequests
<-> fooResponses
?Benoit Quenaudon
11/03/2023, 3:21 PMchannels.create
creates a new Turbine and store it somewhere.Stylianos Gakis
11/03/2023, 3:52 PMAlright, so a convenience over creating the turbine itself. I definitely do wonder why you do that over just creating a tubine, and where it is actually stored afterwards, but if the answer is non-trivial and you don’t have the time to answer then don’t mind that, I will just go with simple turbines and I’ll be fine 😊creates a new Turbine and store it somewhere.channels.create
You could also make `parameters`/`returns` kind of namingAnd yeah `parameters`/`returns` kind of naming would work too, just like `requests`/`responses`. I think I am leaning a bit more towards the latter. I do kinda like having the
turbine
name in there, so that it’s 100% obvious that the test is accessing a test-only function from inside the test double. Otherwise, at a glance, sometimes it can be a bit confusing if the test is doing something with the test-specific types, or if it is somehow accessing some part of the public interface itself. But this is probably nit-picking here.
And yes for the restThat gives me some good confidence to continue doing this. Thanks a lot for taking your time to help me out here 😊
Benoit Quenaudon
11/03/2023, 4:08 PM