Skip to content

Testing

Mike Thompson edited this page Jul 14, 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

We'll start with Event Handlers. They are easy to test 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 structure in app-db and then checking it after every, single event handler. Every single one.

Via middleware, this is easy to arrange. A complete example can be found in the todomvc example:

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 takes an reagent/atom (app-db) parameter, and returns another (via that reaction).

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:

First 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 second step in the refactor, register using a thin reaction wrapper:

(register-sub  
  :completed-count  
   (fn [app-db v]  
     (reaction (completed-count-handler @app-db v))))

completed-count-handler is now doing all the work. That's what we'll want to test. And it is a pure function. I think we are there.

Views - Part 1

Views are the trickiest.

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 it is just a clojure data structure - vectors containing other vectors and maps, etc. Perhaps you'd use https://github.com/nathanmarz/specter to check on the presence of certain values and structures? Maybe.

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
      (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 suggestion is to first setup app-db ... then, in your test, call my-view, and then call the returned value (because my-view is a function returning a function), and then check the value returned.

Which is all possible, but messy.

So we've stumbled a bit at this final hurdle, but prior to this, the story was good.