This library provides utilities for testing re-frame applications.
These utilities:
- allow you to use
subscribein your tests - allow you to use
dispatchin your tests - allow you to run tests on both the JVM and JS platforms
- allow you to create "end to end" integration tests, involving backend servers
For context, please be sure to read the basic testing tutorial in the main re-frame docs before going any further.
This library primarily supports the testing of Event Handlers, but Subscription Handlers get to come along for the ride.
Add the following project dependency:
Requires re-frame >= "1.1.1"
In the namespace where you register your tests, perhaps called tests.cljs, you have 2 things to do.
First, add this require to the ns:
(ns app.tests
(:require
...
[day8.re-frame.test :as rf-test] ;; <-- add this
...))Second, Define or Modify some tests.
(deftest init
(rf-test/run-test-sync ;; <-- add this
;; with the above macro this becomes a dispatch-sync
;; and app-db is isolated between tests
(rf/dispatch [:initialise-db])
;; Define subscriptions to the app state
(let [showing (rf/subscribe [:showing])]
;;Assert the initial state
(is (= :all @showing)))))re-frame-test provides two macros which dovetail with cljs.test.
Execute body as a test, where each dispatch call is executed
synchronously (via dispatch-sync), and any subsequent dispatches which are
caused by that dispatch are also fully handled/executed prior to control flow
returning to your test.
Think of it as though every dispatch in your app had been magically
turned into dispatch-sync, and re-frame had lifted the restriction that says
you can't call dispatch-sync from within an event handler.
This macro is applicable for most events that do not run async behaviour within the event.
From the todomvc example:
(defn test-fixtures
[]
;; change this coeffect to make tests start with nothing
(rf/reg-cofx
:local-store-todos
(fn [cofx _]
"Read in todos from localstore, and process into a map we can merge into app-db."
(assoc cofx :local-store-todos
(sorted-map)))))Define some test-fixtures. In this case we have to ignore the localstore in the tests.
(deftest basic--sync
(rf-test/run-test-sync
(test-fixtures)
(rf/dispatch [:initialise-db])Use the run-test-sync macro to construct the tests and initialise the app state.
Note that, the dispatch will be handled before the following code is executed,
effectively turning it into a dispatch-sync. Also any changes to the database
and registrations will be rolled back at the termination of the test, therefore
our fixtures are run within the run-test-sync macro.
;; Define subscriptions to the app state
(let [showing (rf/subscribe [:showing])
sorted-todos (rf/subscribe [:sorted-todos])
todos (rf/subscribe [:todos])
visible-todos (rf/subscribe [:visible-todos])
all-complete? (rf/subscribe [:all-complete?])
completed-count (rf/subscribe [:completed-count])
footer-counts (rf/subscribe [:footer-counts])]
;;Assert the initial state
(is (= :all @showing))
(is (empty? @sorted-todos))
(is (empty? @todos))
(is (empty? @visible-todos))
(is (= false (boolean @all-complete?)))
(is (= 0 @completed-count))
(is (= [0 0] @footer-counts))
;;Dispatch the event that you want to test, remember that `re-frame-test`
;;will process this event immediately.
(rf/dispatch [:add-todo "write first test"])
;;Test that the dispatch has mutated the state in the way that we expect.
(is (= 1 (count @todos) (count @visible-todos) (count @sorted-todos)))
(is (= 0 @completed-count))
(is (= [1 0] @footer-counts))
(is (= {:id 1, :title "write first test", :done false}
(first @todos)))This macro is applicable for events that do run some async behaviour (usually outside or re-frame, e.g. an http request) within the event.
Run body as an async re-frame test. The async nature means you'll need to
use wait-for any time you want to make any assertions that should be true
after an event has been handled. It's assumed that there will be at least
one wait-for in the body of your test (otherwise you don't need this macro
at all).
Note: unlike regular ClojureScript cljs.test/async tests, wait-for takes
care of calling (done) for you: you don't need to do anything specific to
handle the fact that your test is asynchronous, other than make sure that all
your assertions happen with wait-for blocks.
From the todomvc example:
(deftest basic--async
(rf-test/run-test-async
(test-fixtures)
(rf/dispatch-sync [:initialise-db])Use the run-test-async macro to construct the tests and initialise the app state
note that the dispatch-sync must be used as this macro does not run the dispatch
immediately like run-test-sync. Also any changes to the database
and registrations will be rolled back at the termination of the test, therefore
our fixtures are run within the run-test-async macro.
;;Define subscriptions to the app state
(let [showing (rf/subscribe [:showing])
sorted-todos (rf/subscribe [:sorted-todos])
todos (rf/subscribe [:todos])
visible-todos (rf/subscribe [:visible-todos])
all-complete? (rf/subscribe [:all-complete?])
completed-count (rf/subscribe [:completed-count])
footer-counts (rf/subscribe [:footer-counts])]
;;Assert the initial state
(is (= :all @showing))
(is (empty? @sorted-todos))
(is (empty? @todos))
(is (empty? @visible-todos))
(is (= 0 @completed-count))
;;Dispatch the event that you want to test, remember
;;that re-frame will not process this event immediately,
;;and need to use the `wait-for` macro to continue the tests.
(rf/dispatch [:add-todo "write first test"])
;;Wait for the `:add-todo` event to be dispatched
;;(note, in the use of this macro we would typically wait for
;;an event that had been triggered by the successful return of
;;the async event).
(rf-test/wait-for [:add-todo-finished]
;;Test that the dispatch has mutated the state in the way
;;that we expect.
(is (= [{:id 1, :title "write first test", :done false}] @todos))Here we have assumed that the :add-todo event will make some sort of async
call which will in turn generate an add-todo-finished event when it has finished.
This is not actually the case in the example code.
You will need npm, with:
$ npm install -g karma karma-cli karma-cljs-test karma-junit-reporter karma-chrome-launcherAnd you will need Chrome.
Copyright (c) 2016 Mike Thompson
Distributed under the The MIT License (MIT).