Skip to content

Testing

Mike Thompson edited this page Jul 15, 2015 · 42 revisions

With a re-frame app, there's three things to test:

  1. event handlers
  2. subscription handlers
  3. views

Event Handlers - Part 1

Event Handlers are the easiest of the three because they are pure functions.

First, create an event handler like this:

(defn my-handler
    [db v]
    ... return a modified version of db)

Then, register it in a separate step:

(register-handler  :some-id   [some-middleware]   my-handler)

With this setup, my-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 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:

Subscription Handlers

Here's a subscription handler from the todomvc example:

(register-sub
  :completed-count
  (fn [app-db _]
      (reaction (completed-count (:todos @db)))))

How do we test this?

We could split the handler function from its registration, like this:

(defn get-completed-count
  [app-db _]
  (reaction (completed-count (:todos @db))))

(register-sub
  :completed-count
  get-completed-count)

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.

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 ...

(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:

(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.

Views - Part 1

Views are the trickiest. I don't tend to write tests for mine, so the description that follows is theoretical and not based in practice ...

If a View has a Form-1 structure, then it is fairly easy to test.

A trivial example:

(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?

Remember 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.

Views - Part 2

But what if the View has a subscription (via a Form-2 structure)?

(defn my-view
   [something] 
   (let [val  (subscribe [:query-id])]     <-- reactive subscritpion
      (fn [something]                      <-- returns the render function
         [:div .... using @val in here])))

At this point, I don't have any tricks up my sleeve. I don't have a "pure" way to test this.

My only testing plan would 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.

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). 7. check that the hiccup

Which is all possible, but 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 annimationFrame. And the next animation Frame won't happen until you hand back control to the browser. I think. Untested. Please report back here if you try.

So, we've stumbled a bit at this final hurdle, but prior to this, the testing story for re-frame was pretty good.