@clojurians.net — I’m delighted to be finally giving ghostwheel a try for an app that has both CLJ and CLJS component. I’m starting with server-side CLJ, just because it’s something I was writing. Delighted that setup was so easy — changes to project.clj and clj file was very straightforward, and wrote my first (>defn) which compiled. But when I passed in an incorrect parameter, it didn’t generate an error. Can you help me figure out what I did wrong? Thank you!!!
(ns trello-workflow.mongodb
#:ghostwheel.core{:check true
:num-tests 0}
(:require [monger.core :as mg]
[monger.collection :as mc]
[trello-workflow.env :as tenv]
[ghostwheel.core :as g
:refer [>defn >defn- >fdef => | <- ?]])
(>defn update-user-record
" given trello user name, update the board record in database "
[username record]
[string? map?
=> map?]
(mc/insert-and-return (:db db) "documents" {:name username :hotkeys record}))
REPL interaction
; second argument should be a map
(update-user-record "genekim" "abc")
; expected an error, but none generated...
{:_id #object[org.bson.types.ObjectId 0x2aeae19f "5b67144fe6136b81f10e7d78"], :name "genekim", :hotkeys "abc"}
@genekim – with that configuration Ghostwheel will generate the fspec/fdef and test code, but no actual checking of any sort will be performed.
You can go about this two ways:
- Add the ::g/instrument
or ::g/outstrument
metadata to enable instrumentation with cljs.test or orchestra respectively
- Set ::g/num-tests
to > 0 and call (g/check)
at the bottom of the namespace to do generative testing. However this will only work in ClojureScript for now, test execution isn't implemented for Clojure yet (test generation is though, so you can call the tests with a test-runner of your choice).
Plus this looks like a side-effectful function so generative testing isn't a great idea anyway. Something's in the works for that, but it's still in an early prototype phase.
I recommend you make sure to suffix the function name with !
according to the Clojure guidelines for impure/unsafe functions like this one. This will also ensure that if you enable generative testing on the namespace or higher level, it won't run for functions that do I/O.
Hope this helps!
Also check out the re-frame guide on how to write pure event handlers: https://github.com/Day8/re-frame/blob/master/docs/EffectfulHandlers.md Even if you aren't using re-frame (or ClojureScript for that matter), I think the principles described can be implemented to some degree in any code base with relatively little effort.
Also keep in mind that the compiler-level configuration is only implemented for ClojureScript at the moment so there's no way to disable Ghostwheel in production on Clojure. That's fine as long as you just generate the fdef
and deftest
code with Ghostwheel, but if you do instrumentation like this it would end up in production which may or may not be what you want.
Wonderful, @clojurians.net — thanks for all the advice! Actually, the functionality I’m looking for right away, even before generative testing, is getting the equivalent of:
(defn display-notification
[msg msgtype]
{:pre [(s/valid? string? msg) (s/valid? keyword? msgtype)]})))
In other words, I’d just love some run-time errors when I pass in the wrong types. How can I enable that?
PS: Even for that one function, I found writing the types in Ghostwheel to be a joy. I’ve made it a habit to write
s/valid`` checks for my parameters, and I love your treatment much better. Thx again for your wonderful work!@genekim In that case what you're looking for is spec instrumentation with the orchestra library, which is enabled with the ::g/outstrument
metadata like this:
(>defn ^::g/outstrument update-user-record! ...)
And thank you!
You can do this on the namespace level as well of course, but checking the input and output of every single function at runtime would definitely have a noticeable performance impact.
Alternatively you can just temporarily instrument stuff in the REPL with (st/instrument fn-name)
if you've defined the specs with Ghostwheel without needing the extra metadata, but that wouldn't persist between namespace reloads.
That’s super, @clojurians.net. Thanks! FWIW, I make enough parameter errors that I’ll likely default to enabling at namespace level, and maybe selectively turning off some functions. (Uh, how would one turn off outstrument on a per-function basis?) I can’t wait to try this tonight, and I’ll be sure to write up something about it — I’m excited about your work!
@genekim You just need to override the Ghostwheel metadata on the function level, see step 5 here:
https://github.com/gnl/ghostwheel#getting-started
In this case you can do (>defn ^{::g/outstrument false} fn-name ...)
or (>defn fn-name {::g/outstrument false} ...)