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-banana
network 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, theEventNetwork
type 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
a
andb
be?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-banana
is 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
Arithmetic
example, wherebInput1
andbInput2
are 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
inA
andinB
, 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
Event
s usingsplit
(or rather, split the input into the twoEvent
s required bylogic
). Now you'd havelogic' :: Event t (Either inA inB) -> Behavior t out
.You're kind of stymied converting the output
Behavior
to anEvent
. In version 0.7, thechanges
function inReactive.Banana.Frameworks
had 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 theMoment
monad. In version 0.8, however, thea
is wrapped up as aFuture a
, whereFuture
is an unexported type. (There's an issue on Github re exportingFuture
.)The easiest way to unwrap the
Behavior
is probably just to reimplementinterpretFrameworks
with the appropriate type. (Note that it returns a tuple containing the initial value and the list of subsequent values.) Even thoughFuture
is not exported, you can use itsFunctor
instance:This should do the trick.
Comparison with other FRP frameworks
Contrast this with other frameworks like Gabriel Gonzalez's
mvc
or Ertugrul Söylemez'snetwire
.mvc
requires you to write your program logic as a stateful but otherwise-purePipe a b (State s) ()
, andnetwire
networks have typeWire s e m a b
; in both cases the typesa
andb
expose 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.