Fork me on GitHub
Filipe Silva14:02:45

is it possible to rename a key with s/keys ?

Filipe Silva14:02:11

I'd like to do something like this

(s/def ::msg (s/and string? #(< 20 (count %))))
;; TODO: declaring a spec for a limited :db/id doesn't sound right. Figure out a better way.
(s/def :db/id (into #{} (range 1 10)))
(s/def ::map-datom (s/keys :req [:db/id ::msg]))
(s/def ::simple-tx (s/coll-of ::map-datom :kind vector? :min-count 0 :max-count 3))

Filipe Silva14:02:43

but instead of (s/def :db/id, I'd like to do (s/def ::id

Filipe Silva14:02:18

and then on s/keys say that ::id is required but should show up as :db/id

Filipe Silva14:02:15

I'd like to do this because I don't want to be declaring the spec for the real :db/id, but would like to have an alternative version of it for testing

Alex Miller (Clojure team)14:02:56

in short, no - the whole idea with spec is that attributes are primary and have meaning and maps are just aggregations of attributes

Alex Miller (Clojure team)14:02:57

if you want a broader definition of ::db/id, then your spec should reflect that

Filipe Silva14:02:06

ok, thank you for explaining it to me

Filipe Silva14:02:41

if I want to just generate test data that is a subset of all possible data, should I be using something else?


@filipematossilva: It sounds like you might be complecting generating data with specing. If in your tests you want to generate known id’s that are a subset of valid id’s, you can probably just pass a generator for that set where you need it.

Filipe Silva14:02:19

yes, I think I am

Filipe Silva14:02:55

but s/keys does not support inline value specs (by design, according to the docs), so I don't think I can do that

Filipe Silva14:02:20

(unless, of course, I am misunderstanding what you propose)

Filipe Silva14:02:22

an alternative is to generate the test data with exercise, and then map over the results to change the key

Filipe Silva14:02:30

which sounds fair enough, but I wanted to inquire first

Alex Miller (Clojure team)14:02:41

you can pass generator overrides when doing most of the functions that generate in spec, but you still have to generate data that conforms to the spec

👍 4
Filipe Silva14:02:04

ah, then this was just something I did not know existed


Totally! :-) When I first started with spec I made the mistake of over speccing to get spec to try and generate test data of the right shape… but now prefer the alternative of specing a little more broadly but using :gen ’s to generate the more precise data I want. Obviously it’s all trade offs. Anyway I was just suspecting Filipe might be falling into this same trap.

Filipe Silva14:02:44

yes, I was 🙂

Filipe Silva14:02:18

I'll try to fiddle with the :gen option on s/keys and see where that takes me


:thumbsup: hope it helps

Filipe Silva15:02:11

@rickmoynihan I'm trying to use the :gen key on s/keys, but I don't quite follow how I can use it to replace a single def

Filipe Silva15:02:30

is that possible? or does :gen need to override how everything is generated

Filipe Silva15:02:53

if you have an example of using it with s/keys it would be super useful

Filipe Silva15:02:15

oh, I think this is the idea?

Filipe Silva15:02:20

(s/def :db/id pos-int?)
(s/def ::small-pos-int? (into #{} (range 1 10)))
(s/def ::map-datom (s/keys :req [:db/id]))
(def small-pos-int-gen (s/gen ::map-datom {:db/id #(s/gen ::small-pos-int?)}))
(s/def ::small-id-map-datom (s/keys :req [:db/id]
                                    :gen (fn [] small-pos-int-gen)))

(s/exercise ::small-id-map-datom 10)
;; => ([{:db/id 5} {:db/id 5}] [{:db/id 3} {:db/id 3}] [{:db/id 1} {:db/id 1}] [{:db/id 1} {:db/id 1}] [{:db/id 7} {:db/id 7}] [{:db/id 8} {:db/id 8}] [{:db/id 6} {:db/id 6}] [{:db/id 8} {:db/id 8}] [{:db/id 6} {:db/id 6}] [{:db/id 2} {:db/id 2}])

Filipe Silva15:02:55

making the real spec, then making another spec that has a generator that takes the real spec generator and adds an override


That’s not quite what I was suggesting…


(s/def ::foo integer?)

  (s/def ::foo-map (s/keys :req-un [::foo]))

 ;; The general generator (generates negative integers too)
  (s/exercise ::foo-map);; => ([{:foo -1} {:foo -1}] [{:foo 0} {:foo 0}] [{:foo 0} {:foo 0}] [{:foo 0} {:foo 0}] [{:foo 3} {:foo 3}] [{:foo -2} {:foo -2}] [{:foo -7} {:foo -7}] [{:foo -37} {:foo -37}] [{:foo -2} {:foo -2}] [{:foo -3} {:foo -3}])

;; An overriden tighter generator generating just 1 and 2 values for :football: 

  (s/exercise ::foo-map 10 {::foo-map (fn [] (s/gen #{{:foo 1} {:foo 2}}))});; => ([{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}] [{:foo 1} {:foo 1}] [{:foo 1} {:foo 1}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}] [{:foo 2} {:foo 2}])

❤️ 4

@filipematossilva: You can override on s/exercise, or st/check as well, so you can tailor your generation there rather than in the specs if you want.

Filipe Silva15:02:36

ah on exercise itself

Filipe Silva15:02:47

(s/def :db/id pos-int?)
(s/def ::small-pos-int? (into #{} (range 1 10)))
(s/exercise :db/id 10 {:db/id #(s/gen ::small-pos-int?)})

Filipe Silva15:02:00

I understand now

Filipe Silva15:02:08

thank you for taking the time to make the examples for me

Filipe Silva15:02:16

it was very helpful


essentially s/exercise / st/check are the testing entry points to spec; so you can split the concerns of specing and overriding generation/testing at those entry points… whilst s/conform s/valid? and s/explain-xxx are the checking side of spec — and consequently don’t support generators at all (because they’re a different concern)


at least that’s how I think of it


There are probably more nuanced explanations 🙂

Filipe Silva15:02:08

that sounds much more sensible than I what was attempting


its ok been there, done it, got burned! 🙂