Why can't I use midje to mock a function that throws using slingshot's throw+
Asked Answered
H

2

6

Here's the situation: I'm trying to unit test function A that calls function B. Function B is called in a slingshot try+ block and under certain circumstances it can throw using a slingshot throw+. I want to mock function B in a midje test so that it returns something that the catch in the try+ block will actually catch. I can't seem to create the right thing to throw though. Here's a substantially abbreviated sketch of the code and the test:

(defn function-A
  [param]
  (try+
    (function-B param)
    (catch [:type :user-not-found]
      (do-something))))

(defn function-B
  [param]
  (throw+ [:type :user-not-found]))

(fact "do-something is called"
  (function-A "param") => (whatever is the result of calling do-something)
  (provided
    (function-B "param") =throws=> (clojure.lang.ExceptionInfo. "throw+: {:type :user-not-found}"
                                                                {:object {:type :user-not-found}, :environment {}}
                                                                nil)))

The ExceptionInfo that I'm throwing seems to be roughtly the right thing. I can see this when my application is running through numerous prn statements. However, whatever I try, I can't get the test to work.

I also tried the bit of code below in a repl to see whether I could understand the problem. However, whilst both pieces of code seem to involve identical Exceptions, only one (the pure slingshot one) manages to catch and print "caught it". I think that if I could understand why one works and the other doesn't, I would be able to solve the problem with the unit test.

(try+
  (try
    (throw+ {:type :user-not-found})
    (catch Exception e
      (prn "Caught:  " e)
      (prn "Class:   " (.getClass e))
      (prn "Message: " (.getMessage e))
      (prn "Cause:   " (.getCause e))
      (prn "Data:    " (.getData e))
      (throw e)))
  (catch [:type :user-not-found] p
    (prn "caught it")))

(try+
  (try
    (throw (clojure.lang.ExceptionInfo. "throw+: {:type :user-not-found}"
                                        {:object {:type :user-not-found}, :environment {}}
                                        nil))
    (catch Exception e
      (prn "Caught:  " e)
      (prn "Class:   " (.getClass e))
      (prn "Message: " (.getMessage e))
      (prn "Cause:   " (.getCause e))
      (prn "Data:    " (.getData e))
      (throw e)))
  (catch [:type :user-not-found] p
    (prn "caught it")))
Hoiden answered 12/6, 2013 at 15:35 Comment(0)
A
2

Following slingshot's code for how it generated a throwable (see here, here and here), I found the following (somewhat contrived) way for generating a throwable that would work when just throwing.

(s/get-throwable (s/make-context {:type :user-not-found} "throw+: {:type :user-not-found}" (s/stack-trace) {}))

Which renders the result you were expecting from your example.

(try+
  (try
    (throw (s/get-throwable (s/make-context {:type :user-not-found} "throw+: {:type :user-not-found}" (s/stack-trace) {})))
    (catch Exception e
      (prn "Caught:  " e)
      (prn "Class:   " (.getClass e))
      (prn "Message: " (.getMessage e))
      (prn "Cause:   " (.getCause e))
      (prn "Data:    " (.getData e))
      (throw e)))
  (catch [:type :user-not-found] p
    (prn "caught it")))

Hope it helps.

Anaya answered 12/6, 2013 at 17:53 Comment(1)
That's brilliant. It certainly works. In my unit test code, I just created a function allowing me to say =throws=> (slingshot-exception {:type :user-not-found}. The function is: (defn slingshot-exception [exception-map] (slingshot.support/get-throwable (slingshot.support/make-context exception-map (str "throw+: " map) (slingshot.support/stack-trace) {}))). Many thanks for solving my problem.Hoiden
C
4

That's a really late response but what about the following solution:

(defn ex+ [cause]
  (try
    (throw+ cause)
    (catch Throwable ex
      ex)))

Usage example:

(broken-fn) =throws=> (ex+ {:type :user-not-found})

The benefit is that you don't rely on internal implementation of Slingshot.

Cento answered 5/2, 2016 at 7:44 Comment(1)
This should be the accepted answer. Simple and robust, because it relies on the main slingshot API.Prestige
A
2

Following slingshot's code for how it generated a throwable (see here, here and here), I found the following (somewhat contrived) way for generating a throwable that would work when just throwing.

(s/get-throwable (s/make-context {:type :user-not-found} "throw+: {:type :user-not-found}" (s/stack-trace) {}))

Which renders the result you were expecting from your example.

(try+
  (try
    (throw (s/get-throwable (s/make-context {:type :user-not-found} "throw+: {:type :user-not-found}" (s/stack-trace) {})))
    (catch Exception e
      (prn "Caught:  " e)
      (prn "Class:   " (.getClass e))
      (prn "Message: " (.getMessage e))
      (prn "Cause:   " (.getCause e))
      (prn "Data:    " (.getData e))
      (throw e)))
  (catch [:type :user-not-found] p
    (prn "caught it")))

Hope it helps.

Anaya answered 12/6, 2013 at 17:53 Comment(1)
That's brilliant. It certainly works. In my unit test code, I just created a function allowing me to say =throws=> (slingshot-exception {:type :user-not-found}. The function is: (defn slingshot-exception [exception-map] (slingshot.support/get-throwable (slingshot.support/make-context exception-map (str "throw+: " map) (slingshot.support/stack-trace) {}))). Many thanks for solving my problem.Hoiden

© 2022 - 2024 — McMap. All rights reserved.