More precise output from clojure.spec/valid? in a test?


#1

Hello!

Case: I have json files in my resources. I change them a lot, so I want to check them as part of a unit test.

Current solution use s/valid? in the test. I’d like to see more precise error messages.

;; dogdb.clj
(ns sample.dogdb
  (:require
   [clojure.spec.alpha :as s]))

(s/def ::name string?)
(s/def ::birthyear int?)

(s/def ::dog
  (s/keys
   :req-un
   [::name ::birthyear]))

(s/def ::dogs
  (s/coll-of ::dog))

(s/def ::dogdb
  (s/keys
   :req-un
   [::dogs]))
;; dogdb_test.clj
(ns sample.dogdb-test
  (:require
   [clojure.data.json :as json]
   [clojure.java.io :as io]
   [clojure.spec.alpha :as s]
   [clojure.test :refer :all]
   [clojure.walk :as walk]

   [sample.dogdb :as dogdb]
   ))

(defn load-json-resource [resource-path]
  (-> (io/resource resource-path)
      io/reader
      json/read
      walk/keywordize-keys))

(deftest dogdb-resource
  (testing "resources/dogdb.json is valid dogdb"
    (is (s/valid? ::dogdb/dogdb
                  (load-json-resource "resources/dogdb.json")))))

When I run the unit test with kaocha, I get output like

FAIL in sample.dogdb-test/dogdb-resource (dogdb_test.clj:21)
resources/dogdb.json is valid dogdb
expected: (s/valid? ::dogdb/dogdb
                  (load-json-resource "resources/dogdb.json"))
  actual: (not (s/valid? ::dogdb/dogdb
                  (load-json-resource "resources/dogdb.json")))

I’d like to see what was actual wrong. Was a dog missing name? Was there a cat in the dog list? Was there no :dogs entry top level?

Could I use something else than clojure.test/is? Recommendations much welcome!

Teodor


#2

You can probably use s/explain-data for this.

(t/deftest spec-test
  (t/is (not (::s/problems (s/explain-data int? "s")))))

(t/run-tests)

Testing user

FAIL in (spec-test) (NO_SOURCE_FILE:2)
expected: (not (:clojure.spec.alpha/problems (s/explain-data int? "s")))
  actual: (not (not [{:path [], :pred clojure.core/int?, :val "s", :via [], :in []}]))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.
{:test 1, :pass 0, :fail 1, :error 0, :type :summary}

#3

Right, so I won’t need to replace t/is because when I use s/explain-data, I get a description of what’s actually wrong.

Thanks!