{"id":13393102,"url":"https://github.com/hyperfiddle/rcf","last_synced_at":"2025-04-12T21:34:21.851Z","repository":{"id":47440400,"uuid":"298377464","full_name":"hyperfiddle/rcf","owner":"hyperfiddle","description":"RCF – a REPL-first, async test macro for Clojure/Script","archived":false,"fork":false,"pushed_at":"2023-10-31T19:57:45.000Z","size":1276,"stargazers_count":265,"open_issues_count":37,"forks_count":13,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-04-19T16:14:26.433Z","etag":null,"topics":["clojure","clojurescript"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyperfiddle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"License.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-09-24T19:34:55.000Z","updated_at":"2024-04-06T06:24:24.000Z","dependencies_parsed_at":"2023-02-10T02:01:36.851Z","dependency_job_id":"e6ca3655-f54a-4be6-86fb-08e4a0fd299a","html_url":"https://github.com/hyperfiddle/rcf","commit_stats":{"total_commits":281,"total_committers":10,"mean_commits":28.1,"dds":0.594306049822064,"last_synced_commit":"6d859fee548d5a2c80cf60ba5b0b4ff803e69f18"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperfiddle%2Frcf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperfiddle%2Frcf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperfiddle%2Frcf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperfiddle%2Frcf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyperfiddle","download_url":"https://codeload.github.com/hyperfiddle/rcf/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248637036,"owners_count":21137529,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript"],"created_at":"2024-07-30T17:00:43.136Z","updated_at":"2025-04-12T21:34:21.830Z","avatar_url":"https://github.com/hyperfiddle.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# RCF – REPL-first async test macro for Clojure/Script\n\nRCF 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!\n\n![](https://i.imgur.com/nBOOZq7.png)\n\nFeatures\n* Clojure/Script\n* async tests\n* zero boilerplate\n* natural REPL workflow\n* one key-chord to run tests, no hotkey configuring\n* same-file tests (examples are better than docstrings)\n* no file watchers, no extra windows, no beeping, no latency\n* notebook support – [example NextJournal notebook](https://nextjournal.com/dustingetz/missionary-relieve-backpressure)\n* it's fun! ✅✅✅\n\nRCF is specifically engineered to support [Electric Clojure](https://github.com/hyperfiddle/electric), which we test, document and teach with RCF.\n\nHype quotes:\n* \"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\"\n* \"I use RCF to do leetcode style questions as 'fun practice.' It certainly didn't feel fun before!\"\n* \"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.\"\n* \"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.\"\n\n# Dependency\n\nProject maturity: CLJ is stable, CLJS is experimental, bb is experimental.\n\n```clojure \n{:deps {com.hyperfiddle/rcf {:mvn/version \"20220926-202227\"}}}\n```\n\nChangelog\n* :throws\n* babashka support (experimental)\n* **breaking** don't return final result return nil like comment\n* `20220926-202227` `!` is deprecated, use `tap` instead\n* `20220827-151056` async test forms no longer guaranteed return final result\n* `20220405` maven group-id renamed from `hyperfiddle` to `com.hyperfiddle` for security\n* 2021 Dec 18: clojurescript dependency is now under the :cljs alias, see #25\n* 2021 Oct 20: custom reporters now dispatch on qualified keywords, see #19\n\nCurrent dev priority is improving complex async tests in ClojureScript.\n\n[![JVM](https://github.com/hyperfiddle/rcf/actions/workflows/tests_clj.yml/badge.svg?branch=master)](https://github.com/hyperfiddle/rcf/actions/workflows/tests_clj.yml)\n[![NodeJS](https://github.com/hyperfiddle/rcf/actions/workflows/tests_node.yml/badge.svg?branch=master)](https://github.com/hyperfiddle/rcf/actions/workflows/tests_node.yml)\n[![Browser](https://github.com/hyperfiddle/rcf/actions/workflows/tests_browser.yml/badge.svg?branch=master)](https://github.com/hyperfiddle/rcf/actions/workflows/tests_browser.yml)\n\n# Usage\n\n`(tests)` blocks erase by default (macroexpanding to nothing), which avoids a startup time performance penalty as well as keeps tests out of prod.\n\nIt's an easy one-liner to turn on tests in your dev entrypoint:\n\n```clojure\n(ns user ; user ns is loaded by REPL startup\n  (:require [hyperfiddle.rcf]))\n\n(hyperfiddle.rcf/enable!)\n```\nTests are run when you send a file or form to your Clojure/Script REPL.\n\n```clojure\n(ns example\n  (:require [hyperfiddle.rcf :refer [tests tap %]]))\n\n(tests\n  \"equality\"\n  (inc 1) := 2\n\n  \"wildcards\"\n  {:a :b, :b [2 :b]} := {:a _, _ [2 _]}\n\n  \"unification\"\n  {:a :b, :b [2 :b]} := {:a ?b, ?b [2 ?b]}\n\n  \"unification on reference types\"\n  (def x (atom nil))\n  {:a x, :b x} := {:a ?x, :b ?x}\n  \n  \"multiple tests on one value\"\n  (def xs [:a :b :c])\n  (count xs) := 3\n  (last xs) := :c\n  (let [xs (map identity xs)]\n    (last xs) := :c\n    (let [] (last xs) := :c))\n\n  \"exceptions\"\n  (assert false \"boom\") :throws java.lang.AssertionError\n\n  (tests\n    \"nested tests (is there a strong use case?)\"\n    1 := 1)\n\n  (tests\n    \"REPL bindings work\"\n    (keyword \"a\") := :a\n    (keyword \"b\") := :b\n    (keyword \"c\") := :c\n    *1 := :c\n    *2 := :b\n    *3 := :a\n    *1 := :c                   ; inspecting history does not affect history\n\n    (keyword \"d\") := :d\n    *1 := :d\n    *2 := :c\n    *3 := :b\n    (symbol *2) := 'c          ; this does affect history\n    (symbol *2) := 'd))\n```\n```text\nLoading src/example.cljc...\n✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅Loaded\n```\n\n# Async tests\n\n```Clojure\n(ns example\n  (:require [clojure.core.async :refer [chan \u003e! go go-loop \u003c! timeout close!]]\n            [hyperfiddle.electric :as e]\n            [hyperfiddle.rcf :as rcf :refer [tests tap % with]]\n            [missionary.core :as m]))\n\n(rcf/set-timeout! 100)\n\n(tests\n  \"async tests\"\n  #?(:clj  (tests\n             (future\n               (tap 1) (Thread/sleep 10)        ; tap value to queue\n               (tap 2) (Thread/sleep 200)\n               (tap 3))\n             % := 1                               ; pop queue\n             % := 2\n             % := ::rcf/timeout)\n     :cljs (tests\n             (defn setTimeout [f ms] (js/setTimeout ms f))\n             (tap 1) (setTimeout 10 (fn []\n             (tap 2) (setTimeout 200 (fn []\n             (tap 3)))))\n             % := 1\n             % := 2\n             % := ::rcf/timeout)))\n\n(tests \n  \"electric\"\n  (def !x (atom 0))\n  (def dispose\n    (e/run\n      (let [x (e/watch !x)\n            a (inc x)\n            b (inc x)]\n        (tap (+ a b)))))\n  % := 2\n  (swap! !x inc)\n  % := 4\n  (swap! !x inc)\n  % := 6\n  (dispose))\n\n(tests\n  \"core.async\"\n  (def c (chan))\n  (go-loop [x (\u003c! c)]\n    (when x\n      (\u003c! (timeout 10))\n      (tap x)\n      (recur (\u003c! c))))\n  (go (\u003e! c :hello) (\u003e! c :world))\n  % := :hello\n  % := :world\n  (close! c))\n\n(tests\n  \"missionary\"\n  (def !x (atom 0))\n  (def dispose ((m/reactor (m/stream! (m/ap (! (inc (m/?\u003c (m/watch !x)))))))\n                (fn [_] #_(prn ::done)) #(prn ::crash %)))\n  % := 1\n  (swap! !x inc)\n  (swap! !x inc)\n  % := 2\n  % := 3\n  (dispose)))\n```\n\n# CI\n\nTo run in CI, configure a JVM flag for RCF to generate clojure.test deftests, and then run them with clojure.test. [Github actions example](https://github.com/hyperfiddle/rcf/tree/master/.github/workflows).\n\n```Clojure\n; deps.edn\n{:aliases {:test {:jvm-opts [\"-Dhyperfiddle.rcf.generate-tests=true\"]}}}\n```\n```bash\n% clj -M:test -e \"(require 'example)(clojure.test/run-tests 'example)\"\n\nTesting example\n✅✅✅✅✅✅✅✅\nRan 1 tests containing 8 assertions.\n0 failures, 0 errors.\n{:test 1, :pass 8, :fail 0, :error 0, :type :summary}\n```\n\n# ClojureScript configuration\n\nFor 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.\n\n```Clojure\n(ns dev-entrypoint\n  (:require [example] ; transitive inline tests will erase\n            [hyperfiddle.rcf :refer [tests]]))\n\n; wait to enable tests until after app namespaces are loaded\n(hyperfiddle.rcf/enable!)\n\n; subsequent REPL interactions will run tests\n\n; prevent test execution during cljs hot code reload\n#?(:cljs (defn ^:dev/before-load stop [] (hyperfiddle.rcf/enable! false)))\n#?(:cljs (defn ^:dev/after-load start [] (hyperfiddle.rcf/enable!)))\n```\n\n# FAQ\n\n*One of my tests threw an exception, but the stack trace is empty?* — you want `{:jvm-opts [\"-XX:-OmitStackTraceInFastThrow\"]}` [explanation](https://web.archive.org/web/20190416091616/http://yellerapp.com/posts/2015-05-11-clojure-no-stacktrace.html) (this may be JVM specific)\n\n*I see no output* — RCF is off by default, run `(hyperfiddle.rcf/enable!)`\n\n*Emacs has no output and tests are enabled* — check if your emacs supports emojis\n\n*How do I customize what’s printed at the REPL?* — see [reporters.clj](https://github.com/hyperfiddle/rcf/blob/03c821c3875c3dfe647c945430ecdc5a7c8b594f/src/hyperfiddle/rcf/reporters.clj), [reporters.cljs](https://github.com/hyperfiddle/rcf/blob/03c821c3875c3dfe647c945430ecdc5a7c8b594f/src/hyperfiddle/rcf/reporters.cljs)\n\n# Community\n\n\u0026#35;hyperfiddle @ clojurians.net\n\n![Scroll Of Truth meme saying \"you do not really understand something until you can explain it as a passing test\".](./doc/meme.jpg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperfiddle%2Frcf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyperfiddle%2Frcf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperfiddle%2Frcf/lists"}