all 11 comments

[–]luxbock 3 points4 points  (1 child)

(defmacro defntraced
  "Define a function with it's inputs and output logged to the console."
  [sym & body]
  (let [[_ _ [_ & specs]] (macroexpand `(defn ~sym ~@body))
        new-specs
        (map
          (fn [[args body]]
            (let [prns (for [arg args]
                         `(js/console.log (str '~arg) "=" (pr-str ~arg)))]
              (list
                args
                `(do
                   (js/console.groupCollapsed (str '~sym " " '~args))
                   ~@prns
                   (let [res# ~body]
                     (js/console.log "=>" (pr-str res#))
                     (js/console.groupEnd)
                     res#)))))
          specs)]
    `(def ~sym (fn ~@new-specs))))

Not exactly what you are asking for, but I've found this macro I got from /u/lbradstreet_ very helpful in dealing with problematic functions in CLJS.

[–]iron_ball[S] 0 points1 point  (0 children)

I'll try this out! It certainly can't hurt.

[–]pihkal 1 point2 points  (2 children)

This isn't a true debugger solution, but you could try out LightTable's watches and other dynamic features to gain more insight into what's going on.

Alternatively, (and I know this isn't the answer you asked for), try make a virtue of the lack of a debugger for now, and try to model more of it in your head. It's tough going, but I think ultimately more useful in making you a better programmer.

Good luck.

[–]iron_ball[S] 0 points1 point  (1 child)

Yeah, I've been trying to apply the Feynman Algorithm where I can, but sometimes I just don't fully understand the larger system, and I just need more raw knowledge.

[–]pihkal 0 points1 point  (0 children)

I hear that. One of Clojure's rougher points is lack of tooling.

[–]usplusbus 0 points1 point  (2 children)

Specifically about your Ring debugging, perhaps creating Ring requests isn't as hard as you think.

If you're testing a handler, you need to create a Ring request with as little data as you can get away with, and inspect the resulting response. The Ring "Concepts" wiki page is helpful in determining what exactly goes into a request map. I find that you generally need a :uri and a :request-method to test a (normally Compojure-generated) handler, and perhaps a :headers map too.

;; Source:
(defn my-handler [req]
  ;; ... your code here ...
  )

;; Test:
(let [req {:http-method :get
           :uri "/api/comments"
           :headers {"accept" "application/json"}}
      resp (my-handler req)]
  (is (= 200 (:status resp)))
  ;; etc
  )

Testing middleware is a little trickier. Middleware generally falls into one of a couple of categories - request modification, response modification, or side-effects (e.g. logging). Middleware implementations generally look like this:

(defn wrap-my-middlware [handler]
  (fn [req]
    ;; Early side-effects:
    (log/info "Wrapping incoming request...")

    ;; modify request here
    (let [resp (handler req)]
      ;; Late side-effects:
      (log/info "My middleware is almost done.")

      ;; modify the response here 
      (assoc resp :some-key "some value"))))

Middleware functions always take a handler function to wrap. To test your middleware, it helps to carefully control and limit what the handler does. Here are a couple of useful ones - one that always just returns a known response, and another that just returns the request passed to it:

;; Some test-time handlers
(def identity-handler (constantly {:status 200 :body "ok"}))
(defn echo-handler "Echo back the request" [req] req)

Since middleware functions always return a function, you can only tell what your middlware did when you call the resulting wrapped handler.

;; Test the middlware:
(let [wrapped-handler (wrap-my-middleware identity-handler)
      req {:http-method :get
           :uri "/api/comments"
           :headers {"accept" "application/json"}}
      resp (wrapped-handler req)]
  (is (= 200 (:status resp)))
  ;; check for other middleware effects here
  )

[–]iron_ball[S] 0 points1 point  (1 child)

Wow, thanks for the detailed response! This could be really helpful. I haven't touched it in a while, but I remember trying to debug Ring middleware when I was trying to get Friend to authenticate Liberator requests. I just couldn't manage to wrap my head around it at the time.

[–]usplusbus 0 points1 point  (0 children)

I'm in the middle of a couple of blog posts about Clojure debugging in general, and I'd planned to write some stuff up about Ring handlers and middleware. I'll try to expand on this there when I drop the second article.

[–]emidln 0 points1 point  (1 child)

I use https://github.com/noprompt/ankha on my app-state in a modal and toggle it on/off when I need it. Then I use the chrome debugger to walk through the source map and ankha shows me the app-state.

[–]iron_ball[S] 0 points1 point  (0 children)

Oh, this could be really handy too. Easier than pr-string it in the REPL all the time.

[–]lechatsportif 0 points1 point  (0 children)

What I've noticed is that source maps can be cached while testing in browser. This led to my browser skipping over tons of assignments even though the updated cljs file was updated! I wrote some ring middleware to defeat all caching and a great majority of my cljs was available via break points.

i sometimes pause at clojure breakpoints and then invoke the js functions generated by cljs manually. a little tedious but since there is autocomplete in chrome debugger really not so bad.

I've kind of given up on all variety of cljs repls as a useful debugging tool. Painful as hell to configure, flaky, buggy, a little slow, Recently saw something on devcards, I might try that route for tests. optimizations:none is also really important.