Is there a way to unit test networks created in reactive banana? Say I've built up some network with some input events - is it possible to verify that events have produced some output stream/behaviours have some value after some number of input events. Does it even make sense to do this?
I noticed there are various interpret* functions but couldn't seem to work out how to use them. There's also the Model module which looks ideal for testing but has completely different types to the real implementation.
When you say "unit test," I'm imagining something like
QuickCheck, where you inject a number of inputs into the network and examine the outputs. To do such a thing, we'd need a function along the lines of:Near the end of this answer I demonstrate a variant on one of the
interpret*functions that has a similar type, for a specific type of "network."Mumbo-jumbo about
reactive-banananetwork typesSuch a function is incompatible with the actual type of "entire networks" used in
reactive-banana. Contrast the type of the actual function involving networks:So the type of any network is
forall t. Frameworks t => Moment t (). There are no type variables; no inputs or outputs. Similarly, theEventNetworktype has no parameters. That tells us that all of the input and output is handled via side-effects inIO. It also means there can't really be a function likebecause what would
aandbbe?This is an important aspect of the design of
reactive-banana. It makes writing bindings to an imperative GUI framework easy, for instance. The magic ofreactive-bananais to shuffle around all the side-effects into, as the docs call it, "a single, huge callback function."Furthermore, it's typical for the event network to be intimately associated with the GUI itself. Consider the
Arithmeticexample, wherebInput1andbInput2are both built using the actual input widgets, and the output is bound tooutput, another widget.It would be possible to build a testing harness using "mocking" techniques as in other languages. You could substitute out the actual GUI bindings with bindings to something like
pipes-concurrency. I haven't heard of anybody doing so.How to unit test: Abstract out the logic
Better, you can and should write as much of your program logic as possible in separate functions. If you have two inputs, of types
inAandinB, and one output of typeout, perhaps you can write a function likeThis is almost the right type to use with
interpretFrameworks:You can combine the two input
Events usingsplit(or rather, split the input into the twoEvents required bylogic). Now you'd havelogic' :: Event t (Either inA inB) -> Behavior t out.You're kind of stymied converting the output
Behaviorto anEvent. In version 0.7, thechangesfunction inReactive.Banana.Frameworkshad typeFrameworks t => Behavior t a -> Moment t (Event t a), which you could have used to unwrap theBehavior, although you'd have to do it in theMomentmonad. In version 0.8, however, theais wrapped up as aFuture a, whereFutureis an unexported type. (There's an issue on Github re exportingFuture.)The easiest way to unwrap the
Behavioris probably just to reimplementinterpretFrameworkswith the appropriate type. (Note that it returns a tuple containing the initial value and the list of subsequent values.) Even thoughFutureis not exported, you can use itsFunctorinstance:This should do the trick.
Comparison with other FRP frameworks
Contrast this with other frameworks like Gabriel Gonzalez's
mvcor Ertugrul Söylemez'snetwire.mvcrequires you to write your program logic as a stateful but otherwise-purePipe a b (State s) (), andnetwirenetworks have typeWire s e m a b; in both cases the typesaandbexpose the input to and output from your network. This gets you easy testing, but precludes the "inline" GUI bindings available withreactive-banana. It's a tradeoff.