Skip to content

Checkers for collections and strings

marick edited this page Jan 7, 2011 · 13 revisions

Available in 0.9

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. They are most clearly described in the section on sequential? collections.

These checkers can be nested to form checks of tree-like structures.

These checkers will be chatty. That is, they'll print extra information to help you diagnose a failure.

Some quick examples

'My function returns a sequential?. What can I check?

contains

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.)

just

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

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 and has-suffix

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.

has

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?)

singletons

If your expected result is only a single element, you don't have to enclose it in a collection. The following two examples mean the same thing:

    (f) => (has-prefix [1])
    (f) => (has-prefix 1)

"My function returns a string. What can I check?"

Any checker that makes sense for a sequential?collection makes sense for a string:

     "abc" => (contains "bc") ; true
     "abc" => (contains "ac" :gaps-ok) ; true
     "abc" => (contains "ba" :in-any-order) ; true

     "abc" => (has-suffix "bc") ; true

     "123" => (has every? #(Character/isDigit %))

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)
     "afs" => (contains [\s \a] :in-any-order :gaps-ok)

Note the 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

"My function returns a set. What can I check?"

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?])

"My function returns a map. What can I check?"

     (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] ])

In contrast to examples above, a single pair (or MapEntry) is not interpreted as being in a list. Consider

      [ [:a :b] [1 2] ]   

Is this a single map entry whose key and value are both arrays? Or is it two map entries? Midje interprets it as two map entries.

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.

Clone this wiki locally