Re-frame subs and spec.test/instrument

clojurescript
clojure_spec

#1

Can someone shed some light to this? I’m trying to use clojure.spec.test.alpha/instrument or orchestra-cljs.spec.test/instrument to keep my re-frame subscriptions in check.

I have a re-frame subscription and a function (it totally gets called - I can see console.log showing things when component renders). I wrote an fdef that I deliberately made a wrong spec - :args is wrong, :ret - is a bad spec.

When I call instrument - it returns a vector and that fdef is listed there.

When I call that function manually - the spec fails (as expected)

However, when the sub is called from within the component - it simply ignores instrumentation.

Why would this happen? Can I “force” re-frame to validate things that need to be validated automatically? Without me having to manually call each instrumented fn?


#2

Can you show a bit of code? Maybe the subscription doesn’t see the redefinition of the function that instrument does?


#3

I have a normal re-frame subscription, e.g:

(require '[re-frame.core :refer [reg-sub]]
          '[orchestra-cljs.spec.test :as spec-test])

(s/fdef foo
  :ret int?)

(defn foo [] "fooo")

(reg-sub ::foo foo)

(spec-test/instrument)

The sub value gets dereffed in the component’s render fn, meaning that foo is definitely being called, but it doesn’t say anything about failing spec. If I call the function in the repl (by hand) - it fails the spec (as expected).

Does re-frame do anything special that the instrumented function gets ignored?


#4

This might be the same problem when you have:

$ plk
ClojureScript 1.10.520
cljs.user=> (require '[clojure.spec.alpha :as s])
nil
cljs.user=> (def f (fn [x] (str "hello " x)))
#'cljs.user/f
cljs.user=> (def g (partial f "there"))
#'cljs.user/g
cljs.user=> (s/fdef f :args (s/cat :x number?))
cljs.user/f
cljs.user=> (require '[clojure.spec.test.alpha :as stest])
nil
cljs.user=> (stest/instrument `f)
[cljs.user/f]
cljs.user=> (f "foo")
Execution error - invalid arguments to cljs.user/f at (<cljs repl>:1).
"foo" - failed: number? at: [:x]
cljs.user=> (g)
"hello there"

Instrumentation of f won’t have any effect on g since it has taken the value of f and does not get any chance to see a redefinition of f.
It will work correctly when you define g as:

(def g #(f "there"))

So your re-frame instrumentation may work when you do something like (reg-sub ::foo #(foo)))


#5

Awww… this is indeed what’s happening. I wonder if there’s a better way to deal with this. If adding specs and instrumentation means refactoring all call-sites, then I’m afraid I will have hard time selling the merits of this approach to my team


#6

Writing (reg-sub ::foo #'foo) will likely work too.


#7

IME trying to use var refs with spec’s fdef often doesn’t work well. YMMV