Search code examples
clojuremidje

Midje: having single actual value with multiple arrow functions


Put simply, I would like the code shown below to behave how one could think it should behave, e.g. defining the left-hand side value (here as a trivial anonymous associative collection) just once and applying multiple different arrow functions (asserions) to it in a clean and idiomatic way:

  (deftest test-test
    (fact "some-fact"
      {:a 1 :b 2}
      =>
          (contains {:a odd?}))
      =not=>
          (contains {:a even?})
    )

This code obviously doesn't work: only => arrow actually asserts and =not=> is irrelevant (e.g. if we change even? to odd? there the test still passes).

Maybe it's as simple as wrapping in let or using def, but being new to Clojure I would like to know what is the preferred way.


Solution

  • Every checkable must have a left-hand side, an arrow, and a right-hand side, so this code won't work. I also think that Midje is intended as the unit testing library (testing result of one function for different inputs) and you should instead use Spec or Malli for testing data structures.

    That said, I think you can achieve that result in several ways:

    You can of course repeat your left-hand side value twice:

    (fact "some-fact"
          {:a 1 :b 2} => (contains {:a odd?})
          {:a 1 :b 2} =not=> (contains {:a even?}))
    => true
    

    Or insert it into let:

    (let [my-map {:a 1 :b 2}]
      (fact "some-fact"
            my-map => (contains {:a odd?})
            my-map =not=> (contains {:a even?})))
    => true
    

    You can use every-checker:

    (fact "some-fact"
          {:a 1 :b 2}
          => (every-checker (contains {:a odd? :b even?})
                            #(not (some-> (:a %) even?))))
    => true
    

    You can write some macro, which would transform that code exactly as you wish (if you're really new to Clojure, think twice if you really need to do that):

    (defmacro mfact [doc o & args]
      `(fact ~doc
             ~@(mapcat (fn [[x y]] (list o x y))
                       (partition 2 args))))
    
    (mfact "some-fact"
           {:a 1 :b 2}
           =>
           (contains {:a odd?})
           =not=>
           (contains {:a even?}))
    => true