(defmacro defspec-test | |
([name sym-or-syms] `(defspec-test ~name ~sym-or-syms nil)) | |
([name sym-or-syms opts] | |
(when t/*load-tests* | |
`(def ~(vary-meta name assoc :test `(fn [] | |
(let [check-results# (clojure.spec.test/check ~sym-or-syms ~opts) | |
checks-passed?# (every? nil? (map :failure check-results#))] | |
(if checks-passed?# | |
(t/do-report {:type :pass | |
:message (str "Generative tests pass for " | |
(str/join ", " (map :sym check-results#)))}) | |
(doseq [failed-check# (filter :failure check-results#) | |
:let [r# (clojure.spec.test/abbrev-result failed-check#) | |
failure# (:failure r#)]] | |
(t/do-report | |
{:type :fail | |
:message (with-out-str (clojure.spec/explain-out failure#)) | |
:expected (->> r# :spec rest (apply hash-map) :ret) | |
:actual (if (instance? Throwable failure#) | |
failure# | |
(:clojure.spec.test/val failure#))}))) | |
checks-passed?#))) | |
(fn [] (t/test-var (var ~name))))))) |
In case you get a weird exception when running lein test
check if it is something like this: technomancy/leiningen#2173 . It might save you some frustration ;)
Thanks for the gist @kennyjwilli — very helpful! For what it's worth, here is an ostensibly CLJS-safe (untested in CLJS, but tested in CLJ) code snippet based on yours that 1) generates much less code in defspec-test
and 2) includes all require
s necessary to make it work:
(ns put-this-wherever
(:require
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest]
[clojure.string :as str]
[clojure.test :as test]))
(defn report-results [check-results]
(let [checks-passed? (->> check-results (map :failure) (every? nil?))]
(if checks-passed?
(test/do-report {:type :pass
:message (str "Generative tests pass for "
(str/join ", " (map :sym check-results)))})
(doseq [failed-check (filter :failure check-results)]
(let [r (stest/abbrev-result failed-check)
failure (:failure r)]
(test/do-report
{:type :fail
:message (with-out-str (s/explain-out failure))
:expected (->> r :spec rest (apply hash-map) :ret)
:actual (if (instance? #?(:clj Throwable :cljs js/Error) failure)
failure
(::stest/val failure))}))))
checks-passed?))
#?(:clj
(defmacro defspec-test
([name sym-or-syms] `(defspec-test ~name ~sym-or-syms nil))
([name sym-or-syms opts]
(when test/*load-tests*
`(defn ~(vary-meta name assoc :test
`(fn [] (report-results (stest/check ~sym-or-syms ~opts))))
[] (test/test-var (var ~name)))))))
Exactly what I was looking for! Is there any lib that include this bit of code?
Nice work! Here is a very small change that prints the check result with Expound
(ns put-this-wherever
(:require
[clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest]
[clojure.string :as str]
[clojure.test :as test]
[expound.alpha :as expound]))
(defn report-results [check-results]
(let [checks-passed? (->> check-results (map :failure) (every? nil?))]
(if checks-passed?
(test/do-report {:type :pass
:message (str "Generative tests pass for "
(str/join ", " (map :sym check-results)))})
(doseq [failed-check (filter :failure check-results)]
(let [r (stest/abbrev-result failed-check)
failure (:failure r)]
(test/do-report
{:type :fail
:message (binding [s/*explain-out* (expound/custom-printer {:theme :figwheel-theme})]
(expound/explain-results-str check-results))
:expected (->> r :spec rest (apply hash-map) :ret)
:actual (if (instance? #?(:clj Throwable :cljs js/Error) failure)
failure
(::stest/val failure))}))))
checks-passed?))
#?(:clj
(defmacro defspec-test
([name sym-or-syms] `(defspec-test ~name ~sym-or-syms nil))
([name sym-or-syms opts]
(when test/*load-tests*
`(defn ~(vary-meta name assoc :test
`(fn [] (report-results (stest/check ~sym-or-syms ~opts))))
[] (test/test-var (var ~name)))))))
For the following test:
(s/fdef foo
:args (s/cat :x int? :y int?)
:ret pos-int?)
(defn foo [x y]
(+ x y))
(defspec-test foo-test `foo)
The normal output is:
val: 0 fails at: [:ret] predicate: pos-int?
expected: pos-int?
actual: 0
whereas the output with Expound is:
== Checked expound.problems-test/foo ========
-- Function spec failed -----------
(expound.problems-test/foo 0 0)
returned an invalid value.
0
should satisfy
pos-int?
-------------------------
Detected 1 error
expected: pos-int?
actual: 0
This is fabulous and really should be in a library
Guess what, it's about to be lambdaisland/kaocha#95
for the record, the way to use it is the same as you would use
clojure.spec.test/check
for example: