Testing in reactive-banana

frp, haskell, reactive-banana, reactive-programming

Solution

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:

evalNetwork :: Network a b -> [a] -> IO [b]

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 types

Such a function is incompatible with the actual type of "entire networks" used in `reactive-banana`. Contrast the type of the actual function involving networks:

compile :: (forall t. Frameworks t => Moment t ()) -> IO EventNetwork

So the type of any network is `forall t. Frameworks t => Moment t ()`. There are no type variables; no inputs or outputs. Similarly, the `EventNetwork` type has no parameters. That tells us that all of the input and output is handled via side-effects in `IO`. It also means there can't really be a function like

interpret? :: EventNetwork -> [a] -> IO [b]

because what would `a` and `b` 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 of `reactive-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, where `bInput1` and `bInput2` are both built using the actual input widgets, and the output is bound to `output`, 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` and `inB`, and one output of type `out`, perhaps you can write a function like

logic :: Event t inA -> Event t inB -> Behavior t out

This is almost the right type to use with `interpretFrameworks`:

interpretFrameworks :: (forall t. Event t a -> Event t b) ->
                       [a] -> IO [[b]]

You can combine the two input `Event`s using `split` (or rather, split the input into the two `Event`s required by `logic`). Now you'd have `logic' :: Event t (Either inA inB) -> Behavior t out`.

You're kind of stymied converting the output `Behavior` to an `Event`. In version 0.7, the `changes` function in `Reactive.Banana.Frameworks` had type `Frameworks t => Behavior t a -> Moment t (Event t a)`, which you could have used to unwrap the `Behavior`, although you'd have to do it in the `Moment` monad. In version 0.8, however, the `a` is wrapped up as a `Future a`, where `Future` is an unexported type. (There's an issue on Github re exporting `Future`.)

The easiest way to unwrap the `Behavior` is probably just to reimplement `interpretFrameworks` with the appropriate type. (Note that it returns a tuple containing the initial value and the list of subsequent values.) Even though `Future` is not exported, you can use its `Functor` instance:

interpretFrameworks' :: (forall t. Event t a -> Behavior t b) 
                        -> [a] -> IO (b, [[b]])
interpretFrameworks' f xs = do
    output                    <- newIORef []
    init                      <- newIORef undefined
    (addHandler, runHandlers) <- newAddHandler
    network                   <- compile $ do
        e <- fromAddHandler addHandler
        o <- changes $ f e
        i <- initial $ f e
        liftIO $ writeIORef init i
        reactimate' $ (fmap . fmap) (\b -> modifyIORef output (++[b])) o

    actuate network
    bs <- forM xs $ \x -> do
        runHandlers x
        bs <- readIORef output
        writeIORef output []
        return bs
    i <- readIORef init
    return (i, bs)

This should do the trick.

Comparison with other FRP frameworks

Contrast this with other frameworks like Gabriel Gonzalez's `mvc` or Ertugrul Söylemez's `netwire`. `mvc` requires you to write your program logic as a stateful but otherwise-pure `Pipe a b (State s) ()`, and `netwire` networks have type `Wire s e m a b`; in both cases the types `a` and `b` expose the input to and output from your network. This gets you easy testing, but precludes the "inline" GUI bindings available with `reactive-banana`. It's a tradeoff.

Problem

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.

Original source

Related problems