Personally I recommend writing two generation functions, one that takes a generation depth and generates a full data structure to that depth, and another that takes a generation depth and generates a "narrow" structure to the given depth. This way you can test with the first one to a low depth to see "wide" structure generation, and use a much higher generation depth for the narrow one, using a weighted rng to choose which one to generate when using a with-gen form in the spec.
Or as you've noted, recursive-gen already provides a reasonable solution, in that case you can also use that with spec's facilities to wrap something with a custom generator.
(require '[clojure.test.check.generators :as gen])
(s/def ::bool
boolean?)
(def data-gen (gen/recursive-gen gen/vector gen/boolean))
(s/def ::vec
(s/with-gen
(s/coll-of ::data)
(gen/vector data-gen)))
(s/def ::data
(s/with-gen
(s/or :bool ::bool
:vec ::vec)
data-gen))
For reference, the clojure spec generators are just a reexport of the functions in clojure.test.check.generators, which means that you can use them interchangeably.
well, almost interachangeably - most of the spec apis actually take a no-arg function that returns a generator instead of a generator (to allow for dynamic require/invoke)
I remember wondering why e.g. with-gen
accepts a "generator returning function" instead of the generator itself; can you clarify what you mean by that "dynamic require/invoke" functionality? Something to do with requiring additional namespaces within the function but only if the generator is actually invoked (e.g. in generative testing)?
yes, that
by putting it in a thunk, we can avoid dynaloading the test.check.generators ns
I see, thank you
Oh that's good to know. Thanks for the heads up on that.