Add initial testing docs

This commit is contained in:
Mike Thompson 2017-05-30 00:54:18 +10:00
parent a84d25c0db
commit 960a5409f3
2 changed files with 61 additions and 88 deletions

View File

@ -38,6 +38,7 @@
- [Debugging Event Handlers](Debugging-Event-Handlers.md)
- [Debugging](Debugging.md)
- [Testing](Testing.md)
### Miscellaneous

View File

@ -1,23 +1,6 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Testing](#testing)
- [Event Handlers - Part 1](#event-handlers---part-1)
- [Event Handlers - Part 2](#event-handlers---part-2)
- [Subscription Handlers](#subscription-handlers)
- [Components- Part 1](#components--part-1)
- [Components - Part 2A](#components---part-2a)
- [Components - Part 2B](#components---part-2b)
- [Components - Part 2C](#components---part-2c)
- [Summary](#summary)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
> IGNORE THIS DOCUMENT. IT IS WIP
## Testing
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)
With a re-frame app, there's principally three things to test:
1. Event handlers
@ -37,28 +20,16 @@ First, create an event handler like this:
Then, register it in a separate step:
```clj
(re-event-db
(re-frame.core/reg-event-db
:some-id
[some-interceptors]
my-handler)
my-db-handler)
```
With this setup, `my-db-handler` is available for direct testing.
Your unittests will pass in certain values for `db` and `v`, and then ensure it returns the right (modified version of) `db`.
## Event Handlers - Part 2
Event handlers mutate the value in `app-db` - that's their job.
I'd recommend defining a [Prismatic Schema](https://github.com/Prismatic/schema)
for the value in `app-db` and then checking for correctness after every,
single event handler. Every single one.
Using `after` middleware, this is easy to arrange. The todomvc example shows how:
- [define a Schema](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/db.cljs#L6-L28) for the value in `app-db`
- [create some middleware](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/handlers.cljs#L11-L19)
- [add the middleware](https://github.com/Day8/re-frame/blob/2ba8914d8dd5f0cf2b09d6f3942823a798c2ef5c/examples/todomvc/src/todomvc/handlers.cljs#L46) to your event handlers
Your unittests will pass in certain values for `db` and `v`,
and then ensure it returns the right (modified version of) `db`.
## Subscription Handlers
@ -66,55 +37,46 @@ Here's a subscription handler from [the todomvc example](https://github.com/Day8
```clj
(reg-sub
:completed-count
(fn [db _]
(completed-count (:todos db))))
: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))))
```
How do we test this?
We could split the handler function from its registration, like this:
We could split the computation function from its registration, like this:
```clj
(defn get-completed-count
[app-db _]
(reaction (completed-count (:todos @app-db))))
(defn visible-todos
[[todos showing] _]
(register-sub
:completed-count
get-completed-count)
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos)))
(reg-sub
:visible-todos
(fn [query-v _]
[(subscribe [:todos])
(subscribe [:showing])])
visible-todos) ;; <--- computation function used here
```
That makes `get-completed-count` available for direct testing. But you'll note it isn't a pure function.
It isn't values in, values out. Instead, it is atoms in, atoms out.
That makes `visible-todos` available for direct unit testing.
If this function was on a paint chart, they'd call in "Arctic Fusion" to indicate its
proximity to pure white, while hinting at taints.
We could accept this. We could create tests by passing in a `reagent/atom` holding the
certain values and then checking the values in what's returned. That would work.
The more pragmatic among us might even approve.
But let's assume that's not good enough. Let's refactor for pureness:
The 1st step in this refactor is to create a pure function which actually does ALL the hard work ...
```clj
(defn completed-count-handler
[db v] ;; db is a value, not an atom
..... return a value here based on the values db and v)
```
The 2nd step in the refactor is to register using a thin `reaction` wrapper:
```clj
(register-sub
:completed-count
(fn [app-db v]
(reaction (completed-count-handler @app-db v))))
```
Because `completed-count-handler` is now doing all the work, it is the thing we want
to test, and it is now a pure function. So I think we are there.
## Components- Part 1
## View Functions - Part 1
Components/views are slightly more tricky. There's a few options.
@ -130,7 +92,8 @@ Okay, fine, don't believe me, then!!
Here's how, theoretically, I'd write tests if I wasn't me ...
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.
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.
A trivial example:
```clj
@ -146,37 +109,40 @@ So, here, testing involves passing values into the function and checking the dat
What's returned is hiccup, of course. So how do you test hiccup for correctness?
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.
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.
## Components - Part 2A
## View Functions - Part 2A
But what if the Component 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)?
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)?
```clj
(defn my-view
[something]
(let [val (subscribe [:query-id])] <-- reactive subscription
(fn [something] <-- returns the render function
[:div .... using @val in here])))
[:div .... using @val in here])))
```
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)
2. call `my-view` (with a parameter) which will return `the renderer`
3. call `the renderer` (with a parameter) which will return hiccup
4. check the hiccup structure for correctness.
2. call `my-view` (with a parameter) which will return hiccup
3. check the hiccup structure for correctness.
Continuing on, in a second phase you could then:
5. change the value in `app-db` (which will cause the subscription to fire)
6. call `the renderer` again (hiccup returned).
6. call view functions again (hiccup returned).
7. check that the hiccup
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.
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.
## Components - Part 2B
## View Functions - Part 2B
Or ... instead of the not-very-pure method above, you could use `with-redefs` on `subscribe` to stub out re-frame altogether:
@ -192,9 +158,10 @@ Or ... instead of the not-very-pure method above, you could use `with-redefs` o
..... somehow call or render the component and check the output)))
```
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.
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.
## Components - Part 2C
## View Functions - Part 2C
Or ... you can structure in the first place for easier testing and pure functions.
@ -220,3 +187,8 @@ it is called the [Container/Component pattern](https://medium.com/@learnreact/co
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!
<!-- 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 -->