Search code examples
clojureclojure.spectest.check

Spec: partially overriding generators in a map spec


Assuming I have already defined a spec from which I'd like to generate test data:

(s/def :customer/id uuid?)
(s/def :customer/given-name string?)
(s/def :customer/surname string?)
(s/def :customer/age pos?)
(s/def ::customer
  (s/keys
    :req-un [:customer/id
             :customer/given-name
             :customer/surname
             :customer/age]))

In generating test data, I'd like to override how ids are generated in order to ensure they're from a smaller pool to encourage collisions:

(defn customer-generator
  [id-count]
  (gen/let [id-pool (gen/not-empty (gen/vector (s/gen :customer/id) id-count))]
    (assoc (s/gen ::customer) :id (gen/element id-pool))))

Is there a way I can simplify this by overriding the :customer/id generator in my test code and then just using (s/gen ::customer)? So, something like the following:

(with-generators [:customer/id (gen/not-empty (gen/vector (s/gen :customer/id) id-count)))]
  (s/gen ::customer))

Solution

  • Officially, you can override generators for specs by passing an overrides map to s/gen (See the docstring for more details):

    (s/def :customer/id uuid?)
    (s/def :customer/given-name string?)
    (s/def :customer/surname string?)
    (s/def :customer/age nat-int?)
    (s/def ::customer
      (s/keys
        :req-un [:customer/id
                 :customer/given-name
                 :customer/surname
                 :customer/age]))
    
    (def fixed-customer-id (java.util.UUID/randomUUID))
    fixed-customer-id
    ;=> #uuid "c73ff5ea-8702-4066-a31d-bc4cc7015811"
    (gen/generate (s/gen ::customer {:customer/id #(s/gen #{fixed-customer-id})}))
    ;=> {:id #uuid "c73ff5ea-8702-4066-a31d-bc4cc7015811",
    ;    :given-name "1042IKQhd",
    ;    :surname "Uw0AzJzj",
    ;    :age 104}
    

    Alternatively, there is a library for such stuff named genman, which I developed before :) Using it, you can also write as:

    (require '[genman.core :as genman :refer [defgenerator]])
    
    (def fixed-customer-id (java.util.UUID/randomUUID))
    
    (genman/with-gen-group :test
      (defgenerator :customer/id
        (s/gen #{fixed-customer-id})))
    
    (genman/with-gen-group :test
      (gen/generate (genman/gen ::customer)))