glad you're enjoying the library!
could also do something like this (only implemented functionality for clojure.test; will need additional work if you want to use it in other contexts (cljs, standalone matching, etc)
(ns matcher-combinators.metaconstants
"inspired by Midje's metaconstants
()
in response to a question by @enn on Clojurians slack
()"
(:require [matcher-combinators.test :refer [match?]]
[matcher-combinators.result :as result]
[matcher-combinators.model :as model]
[matcher-combinators.core :as core]
[clojure.test :refer [deftest is]]))
(def ^:dynamic *metaconstants* (atom {}))
;; store original implementations of clojure.test/assert-expr
(defonce assert-methods (methods clojure.test/assert-expr))
(defmethod clojure.test/assert-expr 'match? [msg form]
`(binding [*metaconstants* (atom {})]
;; call original `match? implementation for clojure.test/assert-expr
~((get assert-methods 'match?) msg form)))
(defn metaconst
"For every `(metaconst 'x)` value in the expected, asserts that the
corresponding values in the `actual` are always `=`
useful for asserting that two values in a nested structure are equivalent
without asserting anything else about them"
[v]
(reify core/Matcher
(-matcher-for [this] this)
(-matcher-for [this _] this)
(-match [_this actual]
(if-let [existing-actual (get @*metaconstants* v)]
(if (and existing-actual (= existing-actual actual))
{::result/type :match
::result/value actual
::result/weight 0}
{::result/type :mismatch
::result/value (model/->Mismatch existing-actual actual)
::result/weight 1})
(do
(swap! *metaconstants* assoc v actual)
{::result/type :match
::result/value actual
::result/weight 0})))
(-base-name [_] 'metaconstant)))
(deftest metaconstant-test
(is (match? {:a (metaconst 'a)
:a2 (metaconst 'a)
:id1 (metaconst 'id)
:id2 (metaconst 'other-id)}
{:a 4 :a2 4
:id1 (java.util.UUID/randomUUID)
:id2 (java.util.UUID/randomUUID)})
"the a/a2 values must be equal, but the id values don't need to be")
(let [id (java.util.UUID/randomUUID)]
(is (match? {:id1 (metaconst 'id)
:id2 (metaconst 'id)}
{:id1 id
:id2 id})
"the id values must match"))
;; expected to fail
(is (match? {:a (metaconst 'a)
:a2 (metaconst 'a)}
{:a 5 :a2 4})))