This adds a new test expression type =?
that uses a Methodical multimethod
to decide whether expected
and actual
should be "approximately equal". It dispatches on the types of expected
and actual
.
Now while you can already write all the sort of "approximately equal" things you want in theory using schema=
(defined
in mb.hawk.assert-exprs
, in practice it's a
bit of a hassle. Want to convert an =
to schema=
and change one key in a map to use s/Int
instead of a specific
number? Have fun wrapping every other value in s/eq
. Want to ignore unused keys like partial=
? You need to stick
s/Keyword s/Any
in every. single. map. =?
takes the best of schema=
and partial=
, steals a few ideas from
Expectations, and is more powerful and easier to use than any of
those three.
=
usages can be replaced with =?
with no other changes -- you can replace that one single key with a predicate
function and leave everything else the same.
Here's some rules I've defined already:
-
Two regex patterns that are the exact same pattern should be considered =?. (For some wacko reason regex patterns aren't equal unless they're the same object)
-
An
expected
plain Clojure map should be approximately equal to anactual
record type. We shouldn't need some hack likemt/derecordize
to be able to write tests for this stuff -
an
expected
regex pattern should be approximately equal to anactual
string if the string matches the regex. (This is whatre=
currently does. We can replacere=
with=?
entirely.) -
an
expected
function should be approximately equal to a anactual
value if(expected actual)
returns truthy. -
an
expected
map should be approximately equal to anactual
map if all the keys inexpected
are present inactual
and their respective values are approximately equal. In other words, extra keys inactual
should be ignored (this is what ourpartial=
works) -
Motivating example: two sublcasses of
Temporal
e.g.OffsetDateTime
andZonedDateTime
should be=?
if we would print them exactly the same way.
Defining new =?
behaviors is as simple as writing a new defmethod
.
(methodical/defmethod =?-diff [java.util.regex.Pattern String]
[expected-regex s]
(when-not (re-matches expected-regex s)
(list 'not (list 're-matches expected-regex s))))
Methods are expected to return nil
if things are approximately equal, or a form explaining why they aren't if they
aren't. In this case, it returns something like
(not (re-matches #"\d+cans" "toucans")))
This is printed in the correct place by humanized test output and other things that can print diffs.
Built-in functions for =?
are defined in the mb.hawk.assert-exprs.approximately-equal
namespace. You can create
an alias for this namespace like so:
(require '[mb.hawk.assert-exprs.approximately-equal :as =?])
exactly
means results have to be exactly equal as if by =
. Use this to get around the normal way =?
would
compare things. This works inside collections as well.
(is (=? {:m (=?/exactly {:a 1})}
{:m {:a 1, :b 2}}))
;; =>
expected: {:m (exactly {:a 1})}
actual: {:m {:a 1, :b 2}}
diff: - {:m (not (= (exactly {:a 1}) {:a 1, :b 2}))}
+ nil
schema
compares things to a schema.core
Schema:
(is (=? {:a 1, :b (=?/schema {s/Keyword s/Int})}
{:a 1, :b {:c 2}}))
=> ok
(is (=? {:a 1, :b (=?/schema {s/Keyword s/Int})}
{:a 1, :b {:c 2.0}}))
=>
expected: {:a 1, :b (schema {(pred keyword?) (pred integer?)})}
actual: {:a 1, :b {:c 2.0}}
diff: - {:b {:c (not (integer? 2.0))}}
+ nil
malli
compares things to a malli
schema:
(is (=? {:a 1, :b (=?/malli [:map-of :keyword :int])}
{:a 1, :b {:c 2}}))
=> ok
(is (=? {:a 1, :b (=?/malli [:map-of :keyword :int])}
{:a 1, :b {:c 2.0}}))
=>
expected: {:a 1, :b (malli [:map-of :keyword :int])}
actual: {:a 1, :b {:c 2.0}}
diff: - {:b {:c ["should be an integer"]}}
approx
compares whether two numbers are approximately equal:
;; is the difference between actual and 1.5 less than ±0.1?
(is (=? (=?/approx [1.5 0.1])
1.51))
=> true
(is (=? (=?/approx [1.5 0.1])
1.6))
=>
expected: (approx [1.5 0.1])
actual: 1.6
diff: - (not (approx 1.5 1.6 #_epsilon 0.1))
+ nil