clojure-expectations/clojure-test

No equivalent to thrown-with-msg?

seancorfield opened this issue · 6 comments

clojure.test has thrown-with-msg? which lets you test for both the exception type and the message -- show how this can be done with expectations.

Answer:

(defexpect dbz 
  (expect (more-> ArithmeticException type 
                  "Divide by zero"    ex-message) 
          (/ 1 0)))

These approaches are not equivalent though, because there is no subclass checking going on when using the more-> approach:

This works:

      (expect RuntimeException
              (/ 1 0))

This fails:

      (expect (more-> RuntimeException type)
              (/ 1 0))

So we can use these workarounds:

      (expect (more-> true (-> type (isa? RuntimeException)))
              (/ 1 0))

      (expect (more-> #{RuntimeException} (-> type bases set))
              (/ 1 0))

but I would expect this to be the behaviour, even when using more->.

Another annoyance with checking for exceptions is that when they don't occur, the error message does not show the evaluation result of the expression, which would be quite helpful for figuring out, why there was no exception thrown. Eg.:

(expect RuntimeException
              (/ 1 20))

just shows:

expected: (thrown? RuntimeException (/ 1 20))
  actual: nil

This seems like the issue of the underlying clojure.test/is and how it handles thrown?, but maybe we shouldn't directly use that, since it's a little too dumb.

The lack of information in a thrown? test failure is due to the underlying clojure.test library. The reporting should be improved there.

If you're writing a test for a thrown exception, I'd expect(!) you to know exactly what type it would be. However, if you really do need to test for a parent type, you could always use two separate expect clauses:

(defexpect throw-base-with-message
  (expect RuntimeException (/ 1 0)) ; this does the instance? test
  (expect (more-> #"Divide" ex-message) (/ 1 0)))

I was writing an SQL migration test, which I wanted to keep DB driver agnostic, so I can run test test against both H2 and MySQL.

In such case I'm expecting a java.sql.SQLIntegrityConstraintViolationException, but H2 throws a more specific org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException.

In this case it's also not desired to repeat the function call I'm testing, because it's not a pure function.

I'm writing a migration test, which would add a new column with a unique-ness constraint on it,
Depending on the actual production data, the migration might fail and I would like to observe
that situation in a test, so I know what to expect.

I feel that I'm not doing something right, but can't quite put my finger on it.

I'm also tried to assert the inverse of this exception, but I'm not sure how to achieve the following assertion using expect:

        (is (not= [#:next.jdbc{:update-count 1}]
                  (try
                    (jdbc/execute! conn ["INSERT INTO ..."])
                    (catch Throwable ex
                      (Throwable->map ex)))))

In general, I haven't found any info on how to do the equivalent of (is (not= ...)).

This discussion would work a lot better on a Slack channel. It's hard to tell exactly what you're trying to test at this point since you're asking about something different in each message. Happy to discuss further in the #expectations channel on the Clojurians Slack.