https://kotlinlang.org logo
#squarelibraries
Title
# squarelibraries
s

Stylianos Gakis

10/24/2023, 3:15 PM
When building test-doubles using Turbine, I do it this style very often, where: • I can be informed both about how these functions may have been called (to test that I am calling the right thing with the right parameters) • And I make the functions return from the tubine, so that I can assert the state before, add something to the tubine, and then assert the state after the fact. This ends up making those test doubles look something like this
Copy code
interface 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
Copy code
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?
b

Benoit Quenaudon

11/03/2023, 3:05 PM
I don’t think doing the step where you do
eggTurbine.add(egg)
is meaningfull. Just kill it.
I think the only place we have it is when there is a I/O bridge, like the network. We’re confirm a request with the right params has been sent
This, we do
Copy code
val getPreSignInDataRequests = channels.create<GetPreSignInDataRequest>()
  val getPreSignInDataResponses = Turbine<ApiResult<GetPreSignInDataResponse>>()

  override suspend fun getPreSignInData(request: GetPreSignInDataRequest): ApiResult<GetPreSignInDataResponse> {
    getPreSignInDataRequests.add(request)
    return getPreSignInDataResponses.awaitItem()
  }
s

Stylianos Gakis

11/03/2023, 3:11 PM
I don’t think doing the step where you do
eggTurbine.add(egg)
is meaningfull. Just kill it.
Yeah, 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 your
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
?
b

Benoit Quenaudon

11/03/2023, 3:21 PM
channels.create
creates a new Turbine and store it somewhere.
And yes for the rest
You could also make `parameters`/`returns` kind of naming
s

Stylianos Gakis

11/03/2023, 3:52 PM
channels.create
creates a new Turbine and store it somewhere.
Alright, 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 😊
You could also make `parameters`/`returns` kind of naming
And 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 rest
That gives me some good confidence to continue doing this. Thanks a lot for taking your time to help me out here 😊
b

Benoit Quenaudon

11/03/2023, 4:08 PM
We store there somewhere so that we can create rules which confirm we don’t have unconsummed items in those channels.
thank you color 1