clojure.spec.test integration with clojure.test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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))))))) |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 ;)