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 functions from 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 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 the 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 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.

Views - Part 1

Views are the trickiest.

If a View has a Form-1 structure then it is fairly easy to test because it will be a pure function.

A trivial example:

(defn greet
   [name]
   [:div "Hello " name])

(greet "Wiki")
;;=> [:div "Hello " "Wiki"]

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])]        <--- a reactive value, prone to change
      (fn [something]                         <--- Form2 views return 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 the my-view (twice because it is a function returning a function), and then check the value returned. Which is all possible but messy.

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