The react-testing-library-cljs library provides a ClojureScript functions for the react-testing-library to simplify interop.
For more information about the principles and concepts behind the testing library, you can visit the official react-testing-library website.
-
The
react-testing-library-cljs.screennamespace provides a wrapper around thereact-testing-library'sscreenobject, making it easier to interact with rendered components. -
The
react-testing-library-cljs.rendernamespace wrapsrender,cleanup, andactfrom@testing-library/reactso plain-React (non-Reagent) tests don't import the JS module directly. Reagent users should usereact-testing-library-cljs.reagent.renderinstead. -
The
react-testing-library-cljs.fire-eventnamespace simplifies firing events on rendered components, allowing you to simulate user interactions. -
The
react-testing-library-cljs.withinnamespace scopes queries to a specific element, useful when multiple similar elements exist in the DOM. -
The
react-testing-library-cljs.user-eventnamespace wraps@testing-library/user-eventto simulate realistic user interactions by firing the full sequence of events (focus, keyboard, pointer, input, etc.) rather than a single synthetic event. Requires the@testing-library/user-eventnpm package. -
The
react-testing-library-cljs.mocksnamespace provides a helper to create mock event handlers that record the arguments they are called with. -
The
react-testing-library-cljs.asyncnamespace provides thedeftest-asyncmacro for writing async tests on toolchains without native ClojureScript async (shadow-cljs before 3.4). Requirespromesaon the classpath. On shadow-cljs 3.4+ prefer a native^:asyncdeftest withawait.
-
The
react-testing-library-cljs.reagent.fire-eventsimilar to thereact-testing-library-cljs.fire-event, but callingreagent.core/flushafter every event to trigger re-render. -
The
react-testing-library-cljs.reagent.renderprovides helper function to render reagent components.
Add the library to your project, using the latest version shown in the Clojars badge above:
org.clojars.olecve/react-testing-library-cljs {:mvn/version "0.0.20"}[org.clojars.olecve/react-testing-library-cljs "0.0.20"]Install the npm dependency:
npm install --save-dev @testing-library/reactAdd a :node-test build target:
;; shadow-cljs.edn
{:builds
{:test {:target :node-test
:output-to "out/test.js"}}}Run tests with:
npx shadow-cljs compile test && node --require global-jsdom/register out/test.js(ns my-app.core-test
(:require [cljs.test :refer [deftest is]]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.screen :as screen]))
(defn greeting []
[:h1 "Hello, world!"])
(deftest renders-greeting
(render/render! [greeting])
(is (some? (screen/get-by-text "Hello, world!"))))(ns my-app.events-test
(:require [cljs.test :refer [deftest is]]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.reagent.fire-event :as fire-event]
[react-testing-library-cljs.screen :as screen]
[reagent.core :as r]))
(defn counter []
(let [count (r/atom 0)]
(fn []
[:button {:on-click #(swap! count inc)}
(str "Count: " @count)])))
(deftest click-increments-counter
(render/render! [counter])
(render/act #(fire-event/click (screen/get-by-text "Count: 0")))
(is (screen/get-by-text "Count: 1")))(ns my-app.mocks-test
(:require [cljs.test :refer [deftest is]]
[react-testing-library-cljs.mocks :as mocks]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.fire-event :as fire-event]
[react-testing-library-cljs.screen :as screen]))
(deftest tracks-button-click
(let [[on-click calls] (mocks/create)]
(render/render! [:button {:on-click on-click} "Submit"])
(fire-event/click (screen/get-by-text "Submit"))
(is (= 1 (count @calls)))))Use within to scope queries to a subtree — useful when the same elements appear in multiple places.
In the example below, scoping to the dialog avoids ambiguity with other "Delete" buttons on the page:
(ns my-app.within-test
(:require [cljs.test :refer [deftest is]]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.screen :as screen]
[react-testing-library-cljs.within :as within]))
(defn page []
[:div
[:ul
[:li "Report Q4" [:button "Delete"]]
[:li "Report Q3" [:button "Delete"]]]
[:div {:role "dialog" :aria-label "Confirm deletion"}
[:p "Are you sure you want to delete this report?"]
[:button "Delete"]
[:button "Cancel"]]])
(deftest confirm-dialog-has-correct-actions
(render/render! [page])
(let [dialog (screen/get-by-role "dialog")]
(is (some? (within/get-by-role dialog "button" {:name "Delete"})))
(is (some? (within/get-by-role dialog "button" {:name "Cancel"})))))find-by-* queries (and all user-event calls) return a Promise that resolves when the interaction settles, so tests that use them must await it. Pick the style that matches your toolchain.
Native async — recommended, requires shadow-cljs 3.4+ (ClojureScript 1.12.145+). Mark the test ^:async and await the Promise directly:
(ns my-app.async-test
(:require [cljs.test :refer [deftest is]]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.screen :as screen]))
(deftest ^:async finds-async-element
(render/render! [my-async-component])
(let [element (await (screen/find-by-text "Loaded!"))]
(is (some? element))))deftest-async macro — the compatibility path for older toolchains that lack native async. It awaits each top-level form and requires promesa on the classpath:
(ns my-app.async-test
(:require [cljs.test :refer [is]]
[promesa.core :as p]
[react-testing-library-cljs.async :refer-macros [deftest-async]]
[react-testing-library-cljs.reagent.render :as render]
[react-testing-library-cljs.screen :as screen]))
(deftest-async finds-async-element
(render/render! [my-async-component])
(p/let [element (screen/find-by-text "Loaded!")]
(is (some? element))))Prerequisites:
- GPG configured (lein help gpg)
- Clojars credentials in
~/.lein/profiles.clj:
{:auth {:repository-auth {#"clojars" {:username "USERNAME" :password "CLOJARS_TOKEN"}}}}Run:
bb release