re-frame.test-helpers¶
Helpers for testing views without a browser. A view returns hiccup, and these pure functions walk that data: they find nodes by :data-testid or any other attribute, read their text, and call the event handlers attached to them. That catches what state assertions miss: a view that reads the wrong path or formats a value wrongly, or a button wired to dispatch into the wrong frame.
(defn counter-view [{:keys [n]}]
[:span (th/testid "counter-label") "Count: " n])
(let [tree (counter-view {:n 5})
label (th/find-by-testid tree "counter-label")]
(th/text-content label))
;; => "Count: 5"
The view above needs no frame because its value comes from props. In a test, assert on the returned string with your test runner's equality assertion.
Everything here, the connected view test included, runs on the JVM with no DOM, no React and no act(). It needs a view you can call as a function, as a Reagent view is. A UIx defui that calls use-sub or use-frame only runs inside React's render, so mount it instead, as in Test a view §4. Its companion re-frame.test-support holds the fixtures that reset the runtime between tests; a test that checks both state and views requires both. To assert on rendered HTML markup rather than on structure or handlers, use render-to-string from re-frame.ssr. Test a view walks through a complete view test.
Reading hiccup nodes¶
attrs¶
- Kind: function
- Signature:
- Description: Returns the attrs map of a hiccup node, or
nilwhen it has none. - Example:
children¶
- Kind: function
- Signature:
- Description: Returns everything after the tag and the optional attrs map. The result is always a vector, empty when the node has no children. Non-vector input returns
nil. - Example:
text-content¶
- Kind: function
- Signature:
- Description: Returns the text under
node: every string leaf, with nested components expanded, joined into one string. Numbers become strings andnils are skipped. With no text, the result is"".- This walks data without a browser: it does not insert spaces between elements or account for CSS visibility. Use keyword tags (
:div); a string tag ("div") is itself a string in the walk and contributes to the result.
- This walks data without a browser: it does not insert spaces between elements or account for CSS visibility. Use keyword tags (
- Example:
extract-handler¶
- Kind: function
- Signature:
- Description: Returns the value under
event-keyinnode's attrs map, ornil. Equivalent to(get (attrs node) event-key). - Example:
Finding nodes by attribute¶
These walk the whole tree, expanding components as they go, and work with any attribute keyword: :data-testid, :id, :data-test, or your own.
find-by-attr¶
- Kind: function
- Signature:
- Description: Returns the first node whose attrs map has
attrequal toval, ornilwhen nothing matches. Anilvalmatches any node withoutattr, leaves included, so looking up an unset test id returns the root or another unrelated node instead ofnil. - Example:
find-all-by-attr¶
- Kind: function
- Signature:
- Description: Returns every node whose attrs map has
attrequal toval, in depth-first order, or an empty vector when nothing matches. - Example:
find-by-attr-prefix¶
- Kind: function
- Signature:
- Description: Returns every node whose
attrvalue is a string starting withprefix, or an empty vector when nothing matches. Non-string values never match. - Example:
Finding nodes by testid¶
The same three searches, keyed on :data-testid.
find-by-testid¶
- Kind: function
- Signature:
- Description: Returns the first node whose
:data-testidistest-id, ornil. Equivalent to(find-by-attr tree :data-testid test-id). - Example:
find-all-by-testid¶
- Kind: function
- Signature:
- Description: Returns every node whose
:data-testidistest-id, in depth-first order. Equivalent to(find-all-by-attr tree :data-testid test-id). - Example:
find-by-testid-prefix¶
- Kind: function
- Signature:
- Description: Returns every node whose
:data-testidstarts withprefix. Equivalent to(find-by-attr-prefix tree :data-testid prefix). - Example:
Driving handlers¶
invoke-handler¶
- Kind: function
- Signature:
- Description: Calls the handler under
event-keyonnodewithargsand returns its value. Use it to click a button or change an input in a test.- The handler must be a function. A declarative event vector is not invoked here; use Fresco's testing helpers for Fresco event attributes. Supply any event argument the callback reads, and expect exceptions from the callback to propagate.
- An ordinary
dispatchinside the handler only queues the event, soapp-dbhas not changed yet wheninvoke-handlerreturns. Wait for the result withre-frame.test-support/poll-until, in the same fixture-owned frame the click dispatched into. A handler that callsdispatch-syncdrains in place. - On CLJS, do not wrap the click and the wait in
rf/with-new-frame:poll-untilreturns a Promise at once, so the body returns and destroys the frame before the queued event drains. On the JVM,poll-untilblocks inside the body, so the frame outlives the wait.
- Errors:
invoke-handlerthrows, because a missing handler is usually the bug under test::rf.error/invoke-handler-bad-node:nodeis not a hiccup vector. Afind-by-testidthat matched nothing returnsnil, which lands here.:rf.error/invoke-handler-missing: there is no handler fn underevent-key, including when the node has no attrs map.
- Example:
Authoring testids¶
testid¶
- Kind: function
- Signature:
- Description: Returns an attrs map carrying
:data-testid id, for use in a view; find the node again withfind-by-testid. The 2-arity mergesextrainto the map, and:data-testidalways wins on collision.- In a
rf/reg-viewbody, write the callback with thedispatchthatreg-viewprovides, as below; the closure captures it. A barerf/dispatchin the callback runs after the render scope has unwound, finds no frame in scope (there is no fallback to:rf/default), and raises:rf.error/no-frame-context.
- In a
- Example:
Tree expansion¶
expand-tree¶
- Kind: function
- Signature:
- Description: Expands every component in a hiccup tree by calling it with its args, as Reagent's renderer would: function components, Form-2 components (a function returning the render function) and Form-3 class components. Afterwards, every vector starts with a keyword tag or a non-component value.
- The
find-*functions andtext-contentalready expand as they walk. Callexpand-treeyourself only to re-expand a sub-tree mid-walk. - A Form-3 class expands by calling its stashed
:reagent-renderfunction directly. No React component is created and no lifecycle methods run. - Form-3 detection looks for the tag that reagent-slim's
create-classsets, so a class from stock Reagent'screate-classis not recognised. On the JVM there are no classes to detect.
- The
- Example:
A connected view test¶
A view that subscribes or dispatches needs a frame in scope. Use make-reset-runtime-fixture with an :adapter, which makes :rf/default the ambient frame, then call the view and walk the tree it returns. After a plain dispatch, including one fired by invoke-handler, wait with poll-until. On CLJS the fixture takes :async? true, and the test composes poll-until's Promise under (async done …). Test a view walks through a complete test.
See also¶
- re-frame.core:
dispatch-sync,with-new-frame,make-frame,app-db-valueandcompute-sub, the production functions these tests drive.