-
Notifications
You must be signed in to change notification settings - Fork 129
Checkers for collections and strings
Midje comes with some general-purpose checkers that apply to datatypes that "look like" collections. (For these purposes, a string looks like a collection.) They are just
, contains
, has-prefix
, has-suffix
, and has
.
These checkers can be nested to form checks of tree-like structures. That can lead to confusion, so see this.
These checkers will be chatty. That is, they'll print extra information to help you diagnose a failure.
The following searches for a subsequence:
(f) => (contains [4 5 6])
To succeed, f
's result must be (1) contiguous and (2) in the same order as in the contains
clause. Here are examples:
[3 4 5 700] => (contains [4 5 700]) ; true
[4 700 5] => (contains [4 5 700]) ; false
[4 5 'hi 700] => (contains [4 5 700]) ; false
The :in-any-order
modifier loosens the second requirement:
['hi 700 5 4] => (contains [4 5 700] :in-any-order) ; true
[4 5 'hi 700] => (contains [4 5 700] :in-any-order) ; still false because 'hi is in the middle
The :gaps-ok
modifier loosens the first:
[4 5 'hi 700] => (contains [4 5 700] :gaps-ok) ; true
[4 700 'hi' 5] => (contains [4 5 700] :gaps-ok) ; false because of bad order
The two modifiers can be used at the same time:
[4 700 5] => (contains [4 5 700] :gaps-ok :in-any-order) ; true
[4 5 'hi 700] => (contains [4 5 700] :in-any-order :gaps-ok) ; true
[700 'hi 4 5 'hi] => (contains [4 5 700] :in-any-order :gaps-ok) ; true
Another way to indicate :in-any-order
is to describe what's contained by a set. The following two are equivalent:
[700 4 5] => (contains [4 5 700] :in-any-order)
[700 4 5] => (contains #{4 5 700})
:gaps-ok
can be used with a set. (So can :in-any-order
, but it has no effect.)
A variant of contains
, just
, will fail if the left-hand-side contains any extra values:
[1 2 3] => (just [1 2 3]) ; true
[1 2 3] => (just [1 2 3 4]) ; false
The first of those seems senseless, since you could just use this:
[1 2 3] => [1 2 3]
However, it's required if you want to use checkers in the expected result:
[1 2 3] => [odd? even? odd?] ; false because second-level functions aren't normally treated as checkers.
[1 2 3] => (just [odd? even? odd?]) ; true
Note that this behavior is not recursive.
just
is also useful if you don't care about order:
[1 3 2] => (just [1 2 3] :in-any-order)
[1 3 2] => (just #{1 2 3})
Midje won't complain if you use :gaps-ok
with just
, but it doesn't have any effect on the result.
has-prefix
requires whatever matches the expected result to be at the beginning of the actual result:
[1 2 3] => (has-prefix [1 2]) ; true
[1 2 3] => (has-prefix [2 1]) ; false - order matters
[1 2 3] => (has-prefix [2 1] :in-any-order) ; true
[1 2 3] => (has-prefix #{2 1}) ; true
As with just
, gaps-ok
doesn't mean anything.
has-suffix
is like has-prefix
, except the match has to be at the very end of the actual results.
You can apply Clojure's quantification functions (every?
, some
, and so on) to all the values of sequence:
(f) => (has some odd?)
(f) => (has every? odd?)
Can check if a sequence contains precisely n results, that all match the checker.
;; There are n-of checkers from one-of up to ten-of.
["a"] => (one-of "a")
[:k :w] => (two-of keyword?)
;; ...
[1 3 5 7 9 11 13 15 17] => (nine-of odd?)
["a" "b" "c" "d" "e" "f" "g" "h" "i" "j"] => (ten-of string?)
;; to go above ten-of
(repeat 100 "a") => (n-of "a" 100)
Counts must be exactly n.
["a" "a" "a"] =not=> (one-of "a")
[:k :w :extra :keywords] =not=> (two-of keyword?)
Most often, the expected result can be described as a vector. In that case, you can leave off the surrounding brackets. The following two are equivalent:
(f) => (has-prefix [1 2] :in-any-order)
(f) => (has-prefix 1 2 :in-any-order)
You can't, however, leave off the brackets if you mean "a singleton list containing a collection". That is, these two are different:
(f) => (contains #{1 2} ; (f) contains 1 and 2, in any order.
(f) => (contains [ #{1 2} ] ) ; (f) contains the set #{1 2}
You can drop the brackets when you expect two or more collections, since that's unambiguous:
(f) => (contains #{1 2} #{3 4}) ; (f) contains two sets.
Any checker that makes sense for a sequential?
collection makes sense for a string:
"abc" => (contains "bc")
"abc" => (contains "ac" :gaps-ok)
"abc" => (contains "ba" :in-any-order)
"abc" => (has-suffix "bc")
"123" => (has every? #(Character/isDigit %))
"11" => (two-of \\1)
Strings can also be compared to regular expressions. A regular expression is normally processed as with re-find
, not re-matches
:
"123" => #"\d"
If you want to be explicit, you can type this:
"123" => (contains #"\d")
If you want exact matching, use just
:
"123" => (just #"\d\d\d")
You can also use has-prefix
and has-suffix
:
"12x" => (has-prefix #"\d+")
"x12" => (has-suffix #"\d+")
:in-any-order and :gaps-ok don't make any sense for regular expressions. At least, I don't care to make sense of them.
When a string is used to check a collection, it is applied to each element of the collection. The following two examples mean the same thing:
["1" "2" "3"] => (contains "f" )
["1" "2" "3"] => (contains [ "f" ])
Strings can be compared to individual characters or collections of characters.
"s" => (just \s)
"s" => (contains \s)
"as" => (contains [\s \a] :in-any-order)
"as" => (contains \s \a :in-any-order) ; brackets can be dropped
"afs" => (contains [\s \a] :in-any-order :gaps-ok)
Note that the reverse is not true: you can't directly compare a collection of characters to an expected string. To do that, convert the string into a vector:
[\a \b] => (just "ab") ; false
[\a \b] => (just (vec "ab")) ; true
All string and character comparisons are case-sensitive. If you want case-insensitive comparisons, use regular expressions:
"AB" => (just "ab") ; false
"AB" => #"(?i)ab" ; true
contains
checks for a subset, except that it uses Midje's extended equality:
#{"1" "12" "123"} => (contains [#"1" #"2" #"3"])
Note that you don't have to use a set on the right-hand-side. :in-any-order
and gaps-ok
don't mean anything for sets, but you can use them if you want to confuse people.
just
is set equality, but it also uses Midje's notion of equality:
#{1 2 3} => (just [odd? odd? even?])
(f) => (contains {:a 1, :b 2}) ; These two key-value pairs are allowed. Others may be present.
(f) => (contains {:a even?}) ; :a must be present and have an even value.
(f) => (just {:a even? :b odd?}) ; The map may only have two entries, :a (whose value is even) and :b (whose value is odd)
Maps may also be represented with pairs or MapEntries:
(f) => (contains [ [:a 1] [:b 2] ])
(f) => (contains [:a 1] [:b 2] ) ; Brackets can be dropped.
Although, as is shown just above, brackets can be dropped, don't do that if you wish to speak of a single map entry. Consider this:
(f) => (just [:a :b])
That's interpreted as "(f)
contains two entries, :a
and :b
", not as "(f)
contains a single key-value pair."
You can apply Clojure's quantification functions (every?
, some
, and so on) to all the values of the map:
(f) => (has every? odd?)
:in-any-order
and gaps-ok
have no meaning for maps.
Records can be compared to either records of the same type or to maps. A record means you want to check the type as well as the key-value pairs. A map means any type that has the specified key-value pairs is OK.
(defrecord AB [a b])
(defrecord AB-different-class [a b])
(facts "about comparing records to records"
(AB. 1 2) => (just (AB. 1 2)) ; true.
(AB-different-class. 1 2) => (just (AB. 1 2)) ; false: types do not match.
(AB. 1 2) => (just (AB. odd? even?)) ; true: Checkers are used when found as record values.
(assoc (AB. 1 2) :c 3) => (contains (AB. 1 2))) ; true: Left-hand side is still an AB.
(facts "about comparing records to maps"
(AB. 1 2) => (contains {:a 1}) ; true because record type is ignored.
(AB. 1 2) => (just {:a odd?, :b even?})) ; true
Records can be compared to map entries, using the same rules as for maps:
(fact (AB. 1 2) => (just [[:a 1] [:b 2]]))