Claude-skill-registry clojure-test-check
Property-based testing with clojure.test.check. Use when writing generative tests, creating custom generators, or debugging shrinking behavior. Triggers on test.check imports, prop/for-all, gen/* usage, or questions about property-based testing in Clojure.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clojure-test-check" ~/.claude/skills/majiayu000-claude-skill-registry-clojure-test-check && rm -rf "$T"
skills/data/clojure-test-check/SKILL.mdtest.check
Property-based testing generates random test data to verify properties hold across many inputs. Instead of specific test cases, define universal properties.
Core Pattern
(require '[clojure.test.check :as tc] '[clojure.test.check.generators :as gen] '[clojure.test.check.properties :as prop]) (def my-property (prop/for-all [v (gen/vector gen/small-integer)] (= (count v) (count (reverse v))))) (tc/quick-check 100 my-property) ;; => {:result true, :pass? true, :num-tests 100, ...}
clojure.test Integration
(require '[clojure.test.check.clojure-test :refer [defspec]]) (defspec sort-is-idempotent ;; 100 optional iterations value here (prop/for-all [v (gen/vector gen/small-integer)] (= (sort v) (sort (sort v)))))
Important! Prefer NOT to supply an iterations, prefer the framework defaults
Essential Generators
| Generator | Produces |
|---|---|
| Integers bounded by size |
| Full range integers |
| Non-negative integers |
| true/false |
| Strings |
| Keywords |
| Vectors of g |
| Fixed heterogeneous vectors |
| Random element from coll |
| Choose generator randomly |
See references/cheatsheet.md for complete generator reference.
Generator Combinators
fmap - Transform generated values:
(gen/fmap sort (gen/vector gen/small-integer)) ; sorted vectors (gen/fmap set (gen/vector gen/nat)) ; sets from vectors
such-that - Filter values (use sparingly, only for likely predicates):
(gen/such-that not-empty (gen/vector gen/boolean))
bind - Chain generators (value from first determines second):
(gen/bind (gen/not-empty (gen/vector gen/small-integer)) #(gen/tuple (gen/return %) (gen/elements %)))
let - Macro combining fmap/bind:
(gen/let [a gen/nat b gen/nat] {:sum (+ a b) :a a :b b})
Custom Record Generators
(defrecord User [name id email]) (def user-gen (gen/fmap (partial apply ->User) (gen/tuple gen/string-alphanumeric gen/nat (gen/fmap #(str % "@example.com") gen/string-alphanumeric))))
Recursive Generators
(def json-like (gen/recursive-gen (fn [inner] (gen/one-of [(gen/list inner) (gen/map gen/keyword inner)])) (gen/one-of [gen/small-integer gen/boolean gen/string])))
Shrinking Failures
When a test fails, test.check automatically shrinks to minimal failing case:
;; Fails on vectors containing 42 (tc/quick-check 100 (prop/for-all [v (gen/vector gen/small-integer)] (not-any? #{42} v))) ;; :fail [[-35 -9 42 8 31 ...]] <- original failure ;; :shrunk {:smallest [[42]]} <- minimal case
Critical Pitfalls
Avoid gen/bind when gen/fmap suffices - bind hampers shrinking:
;; Bad: shrinks poorly (gen/let [n (gen/fmap #(* 2 %) gen/nat)] (gen/vector gen/large-integer n)) ;; Good: shrinks well (gen/fmap #(cond-> % (odd? (count %)) pop) (gen/vector gen/large-integer))
Use gen/large-integer for real integer testing - small-integer/nat cap at ~200.
Run at least 100 tests - Size cycles through 0-199, fewer tests means poor coverage.
Don't use such-that for unlikely predicates - Will timeout. Use fmap to construct valid values instead.
See references/advanced.md for sizing/shrinking details.
Common Property Patterns
Roundtrip:
(= x (decode (encode x)))
Idempotence: (= (f x) (f (f x)))
Invariant preservation: (= (count v) (count (sort v)))
Commutativity: (= (f a b) (f b a))
Model comparison: (= (my-impl x) (reference-impl x))
See references/examples.md for generator recipes.