Skip to content

Instantly share code, notes, and snippets.

@kennyjwilli
Created November 5, 2016 01:37
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kennyjwilli/8bf30478b8a2762d2d09baabc17e2f10 to your computer and use it in GitHub Desktop.
Save kennyjwilli/8bf30478b8a2762d2d09baabc17e2f10 to your computer and use it in GitHub Desktop.
clojure.spec.test integration with clojure.test
(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)))))))
@bhb
Copy link

bhb commented Aug 18, 2018

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

@WhittlesJr
Copy link

This is fabulous and really should be in a library

@WhittlesJr
Copy link

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