reactive-banana icon indicating copy to clipboard operation
reactive-banana copied to clipboard

limiting liftIO for better testing

Open joeyh opened this issue 7 years ago • 6 comments

Code in MomentIO can liftIO so there's no good way to mock out the IO parts for testing it.

In my reactive-banana-automation library, I found it useful to wrap MomentIO, producing a MomentAutomation monad that is not an instance of MonadIO. Thus the IO is limited to a few wrapper functions around fromAddHandler, reactimate, stepper, and changes. That allows me to run code like this, and know that it has no external dependencies and should give the same result each time (barring something unexpected in those IO actions).

> runner <- observeAutomation fridge mkSensors
> runner $ \sensors -> fridgeTemperature sensors =: 6
[FridgeRelay PowerOn]
> runner $ \sensors -> fridgeTemperature sensors =: 3
[]
> runner $ \sensors -> fridgeTemperature sensors =: 0.5
[FridgeRelay PowerOff]

I'm not sure how well-founded my hope is that this lets me reproducibly test a reactive-banana event network?

Anyway, I think it would be great if reactive-banana had a limited monad like my MomentAutomation to allow fully mocked testing of event networks like this.

joeyh avatar Apr 21 '18 19:04 joeyh

I think that the fact that things like reactimate choose MomentIO is the problem. We would much rather have

reactimate :: MonadFramework m => Event (IO ()) -> m ()

or something. MomentIO is then the canonical way to eliminate MonadFramework (it becomes an effect handler).

My suggestion is to change all MomentIO returning functions into functions polymorphic in their monad, subject to a MonadFramework constraint (much like how we have MonadMoment). I think this would address this concern.

ocharles avatar May 15 '18 08:05 ocharles

@ocharles Would you accept a patch that made this change? If so, I'm happy to help.

mitchellwrosen avatar May 22 '18 02:05 mitchellwrosen

I would, but I would like @HeinrichApfelmus to review before applying. Maybe it's worth having him confirm my API is sensible. @joeyh, would my proposed API work for you?

ocharles avatar May 22 '18 08:05 ocharles

@ocharles your API would work for me, I think.

-- see shy jo

joeyh avatar May 22 '18 15:05 joeyh

I think the trouble with replacing the concrete data type … MomentIO by a constraint MonadFramework m => … m is that — it may not actually be possible 😅. There are some functions like liftIOLater that I think the monad needs to know about. And then there is execute where the monad occurs in a contravariant position, and things become suddenly very difficult, because now the monad does need to know about IO.

If I understand the issue correctly, this is mainly about testing. When designing the library, I thought that testing should be done on pure combinators only, using Reactive.Banana.Combinators.interpret. I realize that in practice, this may not be the most ideal way to test. Could you describe your testing setup in more detail?

HeinrichApfelmus avatar May 26 '18 19:05 HeinrichApfelmus

Perhaps I'm missing a way to use interpret. I am using combinators like accumB that operate in MonadMoment, and connecting them up to multiple Event sources obtained with fromAddHandler, which is MomentIO, and I want to test with a given sequence of events and check that the Behavior always does the right thing.

An example of what I'm doing is here: http://hackage.haskell.org/package/reactive-banana-automation-0.5.0/docs/Reactive-Banana-Automation.html#t:Automation and testing it here: http://hackage.haskell.org/package/reactive-banana-automation-0.5.0/docs/Reactive-Banana-Automation.html#v:observeAutomation

I don't see a way to use interpret to test a MonadMoment or MomentIO computation. I could use interpretFrameworks, but it allows IO side effects in the Behavior being tested.

-- see shy jo

joeyh avatar May 27 '18 19:05 joeyh