2016-10-19 10:24:19 +00:00
## Testing
2017-05-29 14:54:18 +00:00
This is an introductory, simple exploration of testing re-frame apps. If you want some more help see [re-frame-test ](https://github.com/Day8/re-frame-test )
2016-10-19 10:24:19 +00:00
With a re-frame app, there's principally three things to test:
1. Event handlers
2. Subscription handlers
3. View functions
## Event Handlers - Part 1
Event Handlers are pure functions and consequently easy to test.
First, create an event handler like this:
```clj
(defn my-db-handler
[db v]
... return a modified version of db)
```
Then, register it in a separate step:
```clj
2017-05-29 14:54:18 +00:00
(re-frame.core/reg-event-db
2016-10-19 10:24:19 +00:00
:some-id
[some-interceptors]
2017-05-29 14:54:18 +00:00
my-db-handler)
2016-10-19 10:24:19 +00:00
```
With this setup, `my-db-handler` is available for direct testing.
2017-05-29 14:54:18 +00:00
Your unittests will pass in certain values for `db` and `v` ,
and then ensure it returns the right (modified version of) `db` .
2016-10-19 10:24:19 +00:00
## Subscription Handlers
Here's a subscription handler from [the todomvc example ](https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs ):
```clj
(reg-sub
2017-05-29 14:54:18 +00:00
:visible-todos
;; signal function
(fn [query-v _]
[(subscribe [:todos])
(subscribe [:showing])])
;; computation function
(fn [[todos showing] _] ;; that 1st parameter is a 2-vector of values
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos))))
2016-10-19 10:24:19 +00:00
```
How do we test this?
2017-05-29 14:54:18 +00:00
We could split the computation function from its registration, like this:
2016-10-19 10:24:19 +00:00
```clj
2017-05-29 14:54:18 +00:00
(defn visible-todos
[[todos showing] _]
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos)))
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
(reg-sub
:visible-todos
(fn [query-v _]
[(subscribe [:todos])
(subscribe [:showing])])
visible-todos) ;; < --- computation function used here
2016-10-19 10:24:19 +00:00
```
2017-05-29 14:54:18 +00:00
That makes `visible-todos` available for direct unit testing.
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
## View Functions - Part 1
2016-10-19 10:24:19 +00:00
Components/views are slightly more tricky. There's a few options.
First, I have to admit an ugly secret. I don't tend to write tests for my views.
Hey, don't give me that disproving frown! I have my reasons.
Remember that every line of code you write is a liability. So tests have to earn
their keep - they have to deliver a good cost / benefit ratio. And, in my experience
with the re-frame architecture, the Reagent view components tend to be an unlikely
source of bugs. There's just not much logic in them for me to get wrong.
Okay, fine, don't believe me, then!!
Here's how, theoretically, I'd write tests if I wasn't me ...
2017-05-29 14:54:18 +00:00
If a Components is a [Form-1 ](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-1-a-simple-function )
structure, then it is fairly easy to test.
2016-10-19 10:24:19 +00:00
A trivial example:
```clj
(defn greet
[name]
[:div "Hello " name])
(greet "Wiki")
;;=> [:div "Hello " "Wiki"]
```
So, here, testing involves passing values into the function and checking the data structure returned for correctness.
What's returned is hiccup, of course. So how do you test hiccup for correctness?
2017-05-29 14:54:18 +00:00
hiccup is just a clojure data structure - vectors containing keywords, and maps, and other vectors, etc.
Perhaps you'd use https://github.com/nathanmarz/specter to declaratively check on the presence of certain values and structures? Or do it more manually.
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
## View Functions - Part 2A
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
But what if the View Function has a subscription (via a [Form-2 ](https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function ) structure)?
2016-10-19 10:24:19 +00:00
```clj
(defn my-view
[something]
(let [val (subscribe [:query-id])] < -- reactive subscription
2017-05-29 14:54:18 +00:00
[:div .... using @val in here])))
2016-10-19 10:24:19 +00:00
```
There's no immediately obvious way to test this as a lovely pure function. Because it is not pure.
Of course, less pure ways are very possible. For example, a plan might be:
1. setup `app-db` with some values in the right places (for the subscription)
2017-05-29 14:54:18 +00:00
2. call `my-view` (with a parameter) which will return hiccup
3. check the hiccup structure for correctness.
2016-10-19 10:24:19 +00:00
Continuing on, in a second phase you could then:
5. change the value in `app-db` (which will cause the subscription to fire)
2017-05-29 14:54:18 +00:00
6. call view functions again (hiccup returned).
2016-10-19 10:24:19 +00:00
7. check that the hiccup
2017-05-29 14:54:18 +00:00
Which is all possible, if a little messy, and with one gotcha. After you change the
value in `app-db` the subscription won't hold the new value straight away.
It won't get calculated until the next animationFrame. And the next animationFrame
won't happen until you hand back control to the browser. I think. Untested.
Please report back here if you try. And you might also be able to use `reagent.core/flush` to force the view to be updated.
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
## View Functions - Part 2B
2016-10-19 10:24:19 +00:00
Or ... instead of the not-very-pure method above, you could use `with-redefs` on `subscribe` to stub out re-frame altogether:
```clj
(defn subscription-stub [x]
(atom
(case x
[:query-id] 42)))
(deftest some-test
(with-redefs [re-frame/subscribe (subscription-stub)]
(testing "some rendering"
..... somehow call or render the component and check the output)))
```
2017-05-29 14:54:18 +00:00
For more integration level testing, you can use `with-mounted-component`
from the [reagent-template ](https://github.com/reagent-project/reagent-template/blob/master/src/leiningen/new/reagent/test/cljs/reagent/core_test.cljs ) to render the component in the browser and validate the generated DOM.
2016-10-19 10:24:19 +00:00
2017-05-29 14:54:18 +00:00
## View Functions - Part 2C
2016-10-19 10:24:19 +00:00
Or ... you can structure in the first place for easier testing and pure functions.
The trick here is to create an outer and inner component. The outer sources the data
(via a subscription), and passes it onto the inner as props (parameters).
As a result, the inner component, which does the testable work, is pure and
easily tested. The outer is fairly trivial.
To get a more concrete idea, I'll direct you to another page on this Wiki
which has nothing to do with testing, but it does use this `simple-outer-subscribe-with-complicated-inner-render`
pattern for a different purpose: [[Using-Stateful-JS-Components]]
Note this technique could be made simple and almost invisible via the
use of macros. (Contribute one if you have it).
This pattern has been independently discovered by many. For example, here
it is called the [Container/Component pattern ](https://medium.com/@learnreact/container-components-c0e67432e005#.mb0hzgm3l ).
## Summary
So, we stumbled slightly at the final hurdle with Form-2 Components. But prior
to this, the testing story for re-frame was as good as it gets: you are testing
a bunch of simple, pure functions. No dependency injection in sight!
2017-05-29 14:54:18 +00:00
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE - RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->