marick/Midje

Metaconstants are elided by Integrant's init method

Closed this issue · 2 comments

Sorry to pile on another spurious issue, but maybe you'll have an idea of what's going on here with metaconstants and Integrant. I first submitted an issue there, but the author doesn't suspect that Integrant is the cause of the issue.

(I'll add that even putting the defmethod inside of the fact yields the same behavior)

(defmethod ig/init-key :some/component
  [_ config]
  (:config-key config))

(fact
  (ig/init {:some/component {:config-key ..value..}})

  => {:some/component ..value..})

Produces:

Error Metaconstants (..value..) can't be compared for equality with {}.

Whereas the following passes:

(fact
  (ig/init {:some/component {:config-key "value"}})

  => {:some/component "value"})

Thank you! And thank you for Midje in general. It's a joy to test with it and it has made my code so much better.

TL/DR: The problem you're having is that it's complicated to fit metaconstants into Clojure. The string example that passes is, I think, a perfectly fine test. I'd just use it (maybe putting .. in the string to signal that it's acting like a metaconstant).


Metaconstants were an outgrowth of my typical testing practice in Ruby and other dynamically-typed languages:

  • You have a function that requires three arguments, but the third argument isn't relevant to
    the test you're writing right now. It could be anything.

  • Picking some random value like 383783 can confuse later readers: "Why that particular value?
    Is it important?"

  • So in my Ruby tests, I would typically use strings like "..irrelevant.." - even
    for integer arguments - to make it clear what was going on. That works surprisingly often.

  • When I wrote Midje, I decided to go one step further and make metaconstants their own type
    instead of strings used in a weird way.

  • That made sense because Clojure treats keywords as functions.
    It let Midje do mocking (provided) even when testing code that uses (:key associative-thing) rather than (fun associative-thing). That is, metaconstants were built to implement several protocols that made them look sort of like maps or records.

  • But not entirely. It seemed to me that comparing a metaconstant to a legitimate map or record
    is dangerous. For equality comparison to work, the test would have to specify all the values
    of a map. Then shouldn't the test just hand in a map? That led to this code:

       (equiv [this that]
              (if (or (symbol? that)
                      (= (type that) Metaconstant)
                      (= that unbound-marker))
                (.equals this that)
                (throw (exceptions/user-error
                        (str "Metaconstants (" (.underlying-symbol this) ") can't be compared for equality with " (pr-str that) ".")
                        "If you have a compelling case for equality, please create an issue:"
                       ecosystem/issues-url))))


So it's just something I'll have to work around, huh. Understood.