Generate data from a spec but overriding specific fields

clojure_spec

#1

I’d like to idiomatically and safely generate data from a spec but overriding specific fields - a bit like plumatic schema’s “complete”.

For example, I have a spec ::customer and I want to generate data (for tests) where :customer/nationality is e.g. specifically :albanian, where the spec for :customer/nationality actually allows #{:albanian :french :british}.

  • I don’t want to generate from ::customer and then assoc :albanian over the top of it.
  • I also don’t want to create a whole new ::albanian-customer spec and generate from that.
  • I do want to use a custom generator as I’d like to follow this comment from the spec guide: “Spec does not trust custom generators and any values they produce will also be checked by their associated spec to guarantee they pass conformance.”

After reading the custom generators guide and googling I’m still missing a good way of doing this - even though I think it should be a relatively common ask. What am I missing?

Any help is much appreciated!


#2

As I see it there are two options. s/gen takes an optional overrides map to override any sub-generators with your own. Alternatively, using g/fmap to alter the generated values is an option.

(s/def ::name string?)
(s/def ::age integer?)
(s/def ::person (s/keys :req-un [::name ::age]))

(let [my-generator (s/gen ::person {::age (fn [] (g/return 25))})]
  (g/sample my-generator 3))
;; ({:name "", :age 25} {:name "g", :age 25} {:name "4", :age 25})

(let [my-generator (->> (s/gen ::person)
                        (g/fmap #(assoc % :age 25)))]
  (g/sample my-generator 3))
;; ({:name "", :age 25} {:name "g", :age 25} {:name "4", :age 25})

With the first approach, you get your custom generated values checked against the spec, which is really useful to avoid mistakes (or when the spec changes and you forget to change your custom generators in the tests).

The second approach works but is more brittle IMO.

I’d go with the first one definitely. Also regarding this:

I think you might be misunderstanding the spec guide. It does not say that you shouldn’t use them, it just says that spec will precisely re-check the values you emit from those custom generators to provide the advantages that I outlined about the first method. If anything, it’s a reason to use custom generators instead of fmap or custom assocs.


#3

Thank you! Spec gen taking an overrides map was definitely what I was missing :slight_smile: I think misread my bullet with the quote though… I’d said I DO want to use a custom generator, for exactly the reasons you stated and I quoted!


#4

Have you heard about specmonstah? Seems to be aimed at solving the exact same problem you are describing.


#5

Here’s the precise part of the readme of Specmonstah that allows for overrides https://github.com/reifyhealth/specmonstah/blob/develop/README.md#07-spec-gen-customization-and-omission