clojure-demos/spec-demo/src/core.cljs

88 lines
3.5 KiB
Clojure

(ns core.demo
(:require [clojure.test.check :as tc]
[clojure.spec.alpha :as spec]
[clojure.spec.test.alpha :as stest]
[clojure.test.check.properties :as prop]
[clojure.test.check.generators :as gen]))
;; We can define new specs with spec/def often aliased as s/def, this takes a namespace's keyword and a spec
;; there are a lot of built in specs like int? pos-int? string? etc you can also use sets
(spec/def ::id pos-int?)
(spec/def ::username string?)
(spec/def ::age (spec/int-in 0 100))
;; example namespaced version the above is namespaced to core.demo, this is a set of allowed characters
(spec/def :core/hex-digit #{"0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F"})
;; Check if a value conforms to a spec
(spec/valid? ::id "ABC-123")
(spec/valid? ::id 4)
(spec/valid? ::age 99)
(spec/valid? ::age 101)
;; we can create more custom specs like a html hex colour code, there is no built in so we
;; need a custom generator in this situation, we create a function that can generate the data
(spec/def ::demo (spec/spec string? :gen (fn [] (gen/return "123"))))
;; usually nicer to make a custom function, the blow fn create a vector of 6 hex characters
;; converts it into a string and append # to the beginning
(defn gen-hex-colour []
(gen/fmap #(str "#" (apply str %)) (gen/vector (spec/gen :core/hex-digit) 6)))
;; define the colour spec it has to pass the regex below, string? would never generate the correct Combination
;; so we supply a way of generating the data
(spec/def :core/hex-colour
(spec/spec
(spec/and string? #(re-matches #"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" %))
:gen gen-hex-colour))
;; we can also spec out hash maps, this is very useful when dealing with json api's
;; we can fake a response data set but also validate the real response to catch changes at the edges of our code
(spec/def ::user
(spec/keys :req-un [::id ::username]
:opt-un [::age]))
;; We can also tests functions by defining the inputs and outputs to the functions and using gen/exercise to repeatedly send data.
;; here we are making sure an integer goes in and an integer always comes out
;; if nil or some other type is returned stest/check will fail
(spec/fdef demo-function
:args (spec/cat :value int?)
:ret int?)
(defn demo-function [value] (+ 5 value))
(stest/instrument `demo-function)
(stest/check `demo-function)
;; to generate a single value we use generate we can also use sample to get a list of values
(gen/generate (spec/gen ::id))
(gen/generate (spec/gen ::age))
(gen/generate (spec/gen ::user))
(gen/sample (spec/gen ::user))
(gen/sample (spec/gen ::id))
(gen/sample (spec/gen ::username))
(gen/sample (spec/gen ::age))
(gen/sample (spec/gen :core/hex-colour))
;; Bonus simple spec that you can use to insert dummy data into the demo datascript example
(spec/def :user/name string?)
(spec/def :room/name string?)
(spec/def :room/link string?)
(spec/def :room/description string?)
(spec/def :datalog/room (spec/keys
:req [:room/name]
:opt [:room/link :room/description]))
(spec/def :user/rooms (spec/coll-of :datalog/room :kind vector?))
(spec/def :datalog/user (spec/keys :req [:user/name]
:opt [:user/rooms]))
(gen/sample (spec/gen :datalog/user))
(def datalog-data (gen/sample (spec/gen :datalog/user)))
;; we can just generate a sample straight into the datalog transact function like below
;;(d/transact! conn (gen/sample (spec/gen :datalog/user)))