RCF turns your Rich Comment Forms into tests (in the same file as your functions). Send form or file to REPL to run tests and it squirts dopamine ✅✅✅. It's good, try it!
Features
- Clojure/Script
- async tests
- zero boilerplate
- natural REPL workflow
- one key-chord to run tests, no hotkey configuring
- same-file tests (examples are better than docstrings)
- no file watchers, no extra windows, no beeping, no latency
- notebook support – example NextJournal notebook
- it's fun! ✅✅✅
RCF is specifically engineered to support Electric Clojure, which we test, document and teach with RCF.
Hype quotes:
- "RCF has changed my habits with regards to tests. It is so much easier than flipping back and forth between files, you get my preferred work habits - work in a comment block until something works. But before RCF I never took the time to turn comment blocks into an automated test"
- "I use RCF to do leetcode style questions as 'fun practice.' It certainly didn't feel fun before!"
- "I think people make the mistake of comparing this with other methods of inlining tests near their function definitions. The integration with the REPL, low syntax/interface, reduces friction and makes testing more attractive as a language of communication and verification."
- "I used RCF in a successful interview. RCF was a massive help in communication and a fast tool for thought whilst under the conditions of technical interview."
Project maturity: CLJ is stable, CLJS is experimental, bb is experimental.
{:deps {com.hyperfiddle/rcf {:mvn/version "20220926-202227"}}}Changelog
- :throws
- babashka support (experimental)
- breaking don't return final result return nil like comment
20220926-202227!is deprecated, usetapinstead20220827-151056async test forms no longer guaranteed return final result20220405maven group-id renamed fromhyperfiddletocom.hyperfiddlefor security- 2021 Dec 18: clojurescript dependency is now under the :cljs alias, see #25
- 2021 Oct 20: custom reporters now dispatch on qualified keywords, see #19
Current dev priority is improving complex async tests in ClojureScript.
(tests) blocks erase by default (macroexpanding to nothing), which avoids a startup time performance penalty as well as keeps tests out of prod.
It's an easy one-liner to turn on tests in your dev entrypoint:
(ns user ; user ns is loaded by REPL startup (:require [hyperfiddle.rcf])) (hyperfiddle.rcf/enable!)
Tests are run when you send a file or form to your Clojure/Script REPL.
(ns example (:require [hyperfiddle.rcf :refer [tests tap %]])) (tests "equality" (inc 1) := 2 "wildcards" {:a :b, :b [2 :b]} := {:a _, _ [2 _]} "unification" {:a :b, :b [2 :b]} := {:a ?b, ?b [2 ?b]} "unification on reference types" (def x (atom nil)) {:a x, :b x} := {:a ?x, :b ?x} "multiple tests on one value" (def xs [:a :b :c]) (count xs) := 3 (last xs) := :c (let [xs (map identity xs)] (last xs) := :c (let [] (last xs) := :c)) "exceptions" (assert false "boom") :throws java.lang.AssertionError (tests "nested tests (is there a strong use case?)" 1 := 1) (tests "REPL bindings work" (keyword "a") := :a (keyword "b") := :b (keyword "c") := :c *1 := :c *2 := :b *3 := :a *1 := :c ; inspecting history does not affect history (keyword "d") := :d *1 := :d *2 := :c *3 := :b (symbol *2) := 'c ; this does affect history (symbol *2) := 'd))
Loading src/example.cljc...
✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅Loaded
(ns example (:require [clojure.core.async :refer [chan >! go go-loop <! timeout close!]] [hyperfiddle.electric :as e] [hyperfiddle.rcf :as rcf :refer [tests tap % with]] [missionary.core :as m])) (rcf/set-timeout! 100) (tests "async tests" #?(:clj (tests (future (tap 1) (Thread/sleep 10) ; tap value to queue (tap 2) (Thread/sleep 200) (tap 3)) % := 1 ; pop queue % := 2 % := ::rcf/timeout) :cljs (tests (defn setTimeout [f ms] (js/setTimeout ms f)) (tap 1) (setTimeout 10 (fn [] (tap 2) (setTimeout 200 (fn [] (tap 3))))) % := 1 % := 2 % := ::rcf/timeout))) (tests "electric" (def !x (atom 0)) (def dispose (e/run (let [x (e/watch !x) a (inc x) b (inc x)] (tap (+ a b))))) % := 2 (swap! !x inc) % := 4 (swap! !x inc) % := 6 (dispose)) (tests "core.async" (def c (chan)) (go-loop [x (<! c)] (when x (<! (timeout 10)) (tap x) (recur (<! c)))) (go (>! c :hello) (>! c :world)) % := :hello % := :world (close! c)) (tests "missionary" (def !x (atom 0)) (def dispose ((m/reactor (m/stream! (m/ap (! (inc (m/?< (m/watch !x))))))) (fn [_] #_(prn ::done)) #(prn ::crash %))) % := 1 (swap! !x inc) (swap! !x inc) % := 2 % := 3 (dispose)))
To run in CI, configure a JVM flag for RCF to generate clojure.test deftests, and then run them with clojure.test. Github actions example.
; deps.edn {:aliases {:test {:jvm-opts ["-Dhyperfiddle.rcf.generate-tests=true"]}}}
% clj -M:test -e "(require 'example)(clojure.test/run-tests 'example)"
Testing example
✅✅✅✅✅✅✅✅
Ran 1 tests containing 8 assertions.
0 failures, 0 errors.
{:test 1, :pass 8, :fail 0, :error 0, :type :summary}For CLJS tests to run, rcf/enable! must be true in both CLJ (shadow-cljs macroexpansion time) and CLJS (JS runtime). Reports may be printed to browser console instead of the REPL, because browser REPLs donn't intercept the async println.
(ns dev-entrypoint (:require [example] ; transitive inline tests will erase [hyperfiddle.rcf :refer [tests]])) ; wait to enable tests until after app namespaces are loaded (hyperfiddle.rcf/enable!) ; subsequent REPL interactions will run tests ; prevent test execution during cljs hot code reload #?(:cljs (defn ^:dev/before-load stop [] (hyperfiddle.rcf/enable! false))) #?(:cljs (defn ^:dev/after-load start [] (hyperfiddle.rcf/enable!)))
One of my tests threw an exception, but the stack trace is empty? — you want {:jvm-opts ["-XX:-OmitStackTraceInFastThrow"]} explanation (this may be JVM specific)
I see no output — RCF is off by default, run (hyperfiddle.rcf/enable!)
Emacs has no output and tests are enabled — check if your emacs supports emojis
How do I customize what’s printed at the REPL? — see reporters.clj, reporters.cljs
#hyperfiddle @ clojurians.net