Hi! Thanks for ClojureScript - we're using it more and more and it's amazing.
Something has been bothering me about testing in the repl, and I figured out what it is today.
Check this out:
harold@straylight:~/src/cljs-test-exception$ cat deps.edn
{:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}}
harold@straylight:~/src/cljs-test-exception$ clj -M --main cljs.main --repl
ClojureScript 1.11.132
cljs.user=> (require '[cljs.test :refer [deftest]])
nil
cljs.user=> (deftest foo [] (throw (js/Error. "oops!")))
#'cljs.user/foo
cljs.user=> (foo)
ERROR in (foo) (Error:NaN:NaN)
Uncaught exception, not in assertion.
expected: nil
actual: #object[Error Error: oops!]
nil
When I have a test that's calling into buggy code and an exception is thrown, I don't get the stack trace.
Compare this with clojure.test
, which has more helpful output:
user> (require '[clojure.test :refer [deftest]])
nil
user> (deftest foo [] (throw (Exception. "Ooops!")))
#'user/foo
user> (foo)
ERROR in (foo) (NO_SOURCE_FILE:13)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.Exception: Ooops!
at user$fn__10841.invokeStatic (NO_SOURCE_FILE:13)
user/fn (NO_SOURCE_FILE:13)
clojure.test$test_var$fn__9894.invoke (test.clj:717)
clojure.test$test_var.invokeStatic (test.clj:717)
clojure.test$test_var.invoke (test.clj:708)
user$foo.invokeStatic (NO_SOURCE_FILE:13)
user$foo.invoke (NO_SOURCE_FILE:13)
user$eval10844.invokeStatic (NO_SOURCE_FILE:15)
user$eval10844.invoke (NO_SOURCE_FILE:15)
clojure.lang.Compiler.eval (Compiler.java:7700)
nrepl.middleware.interruptible_eval$evaluator$run__1435$fn__1446.invoke (interruptible_eval.clj:106)
nrepl.middleware.interruptible_eval$evaluator$run__1435.invoke (interruptible_eval.clj:101)
nrepl.middleware.session$session_exec$session_loop__1519.invoke (session.clj:230)
nrepl.SessionThread.run (SessionThread.java:21)
nil
Maybe cljs.test could be improved to also include this useful information. I believe it would help with repl test/debugging workflows.
Thanks so much for your time and consideration.
Edit:
Eugene makes an interesting point in the comments.
There is a similar difference in exception/error printing:
In cljs:
harold@straylight:~/src/cljs-test-exception$ clj -M --main cljs.main --repl
ClojureScript 1.11.132
cljs.user=> (ex-info "hi" {})
#error {:message "hi", :data {}}
cljs.user=>
In clj:
user> (ex-info "hi" {})
#error {
:cause "hi"
:data {}
:via
[{:type clojure.lang.ExceptionInfo
:message "hi"
:data {}
:at [user$eval8563 invokeStatic "NO_SOURCE_FILE" 11]}]
:trace
[[user$eval8563 invokeStatic "NO_SOURCE_FILE" 11]
[user$eval8563 invoke "NO_SOURCE_FILE" 11]
[clojure.lang.Compiler eval "Compiler.java" 7700]
[nrepl.middleware.interruptible_eval$evaluator$run__1435$fn__1446 invoke "interruptible_eval.clj" 106]
[nrepl.middleware.interruptible_eval$evaluator$run__1435 invoke "interruptible_eval.clj" 101]
[nrepl.middleware.session$session_exec$session_loop__1519 invoke "session.clj" 230]
[nrepl.SessionThread run "SessionThread.java" 21]]}
However, in the testing case, clojure.test
explicitly prints the stack trace:
https://github.com/clojure/clojure/blob/fb22fd778a272b034684a4ee94509552b46ee8a9/src/clj/clojure/test.clj#L394
While cljs does not:
https://github.com/clojure/clojurescript/blob/d701b452b3f0b09f13191094bff9d5763a449191/src/main/cljs/cljs/test.cljs#L338-L344
It does still seem like the Error object is being swallowed, I don't see a way to gain access to it after running the test that threw.