Skip to content

Checking sequential collections

marick edited this page Mar 19, 2013 · 12 revisions

Executable examples

The checkers relevant to sequential collections are just, contains, has-prefix, has-suffix, has and n-of. They apply to strings, too, but the details are more helpfully explained separately.

A sequential collection on the right-hand side of a prediction is checked using equality. As is typical for Clojure, that means that lists and vectors and lazy sequences can all be equal to one another:

(fact 
  '(1 2 3) => [1 2 3]
  [1 '(2) 3] => '(1 [2] 3)
  (map inc (range 0 3)) => [1 2 3])

just - the actual and expected results must have the same number of elements.

If you want a comparison that uses extended equality instead of equality, you can use just:

(fact "just uses extended equality"
  [1 2 3] => (just [odd? even? odd?])
  ["a" "aa" "aaa"] (just [#"a+" #"a+" #"a+"]))

As a side note, the second prediction can be more economically written like this:

(fact ["a" "aa" "aaa"] => (three-of #"a+"))

three-of (more generally, n-of, is covered below.)

Since just always takes a collection, wrapping the expected collection in brackets or quoted parentheses is redundant. So they can be omitted.

(fact "when `just` takes no options, you can omit brackets"
  [1 2 3] => (just odd? even? odd?))

Whether you do that is very much a matter of taste.

just isn't as convenient for trees as it is for flat lists because extended equality in just is not recursive. That is:

(fact "extended equality only applies to the top level"
  [[[1]]] =not=> (just [[[odd?]]]))

If you want extended equality to apply to embedded structures, you have to use just (or other checkers) at each level:

(fact "you have to do this"
  [[[1]]] => (just (just (just odd?))))

At some point, I hope to see checkers tailored to make terse expected results for tree structures, but they don't exist yet.

order

It's sometimes the case that you care about the contents of the sequence, but not the order. You can do that by using a set in just:

(fact [2 1 3] => (just #{1 2 3}))

... but it's perhaps better to be explicit with the :in-any-order flag:

(fact "you can specify that order is irrelevant"
  [2 1 3] => (just [1 2 3] :in-any-order))

(Note: you can leave out the brackets in this case as well, but that looks creepy to me.)

Midje goes to some effort not to be fooled by overcommitting to matches. For example, consider this:

(fact [1 3] => (just [odd? 1] :in-any-order))

If Midje decided that the actual value 1 matched odd?, then a mismatch of 3 and the 1 after odd? would lead to a spurious failure. But Midje correctly discovers the match in the alternate order.

contains - the actual result may have extra elements

contains searches for a matching subsequence of the actual results:

(fact "contains requires only a subset to match"
  [1 2 3] => (contains even? odd?))

(Note that contains, like just, allows you to omit brackets.)

contains searches for a contiguous sequence of matches. That is:

(fact [1 2 3] =not=> (contains odd? odd?))

If you want to relax that requirement, use :gaps-ok:

(fact [1 2 3] => (contains [odd? odd?] :gaps-ok))

contains, like just, supports :in-any-order. Here's an example that uses both it and :gaps-ok.

(fact [5 1 4 2] => (contains [1 2 5] :gaps-ok :in-any-order))

You can also use a set if you want to be more terse:

(fact [5 1 4 2] => (contains #{1 2 5} :gaps-ok))

Note: the algorithm used for this complicated case is inefficient and also won't work on longer sequences. I keep hoping someone will replace it.

has-prefix and has-suffix for anchored subsequences

Whereas contains finds matches anywhere within the sequence, has-prefix forces the match to be at the beginning:

(fact "has-prefix is anchored to the left"
    [1 2 3] =not=> (has-prefix [2 3])     ; it's not a prefix
    [1 2 3] =>     (has-prefix [1 2])     
    [1 2 3] =not=> (has-prefix [2 1])     ; order matters
    [1 2 3] =>     (has-prefix [2 1] :in-any-order)
    [1 2 3] =>     (has-prefix #{2 1}))

has-suffix is like has-prefix, except the match has to be at the very end.

n-of and friends

These are used to avoid giving just a sequence of n identical elements:

(fact "one checker to match exactly N elements."
  ["a"] => (one-of "a")
  [:k :w] => (two-of keyword?)
  ["a" "aa" "aaa"] => (three-of #"a+")
  ;; ...
  [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))

Note that n-of and friends use extended equality. So here's how you might check for a 2x2 array of even numbers:

(fact "the result is an even square"
  [[2 4]
   [6 8]] => (two-of (two-of even?)))

has checks a property against some or all of the sequence values

You can apply Clojure's quantification functions (every?, some, and so on) to all the values of a sequence:

(fact 
  [2 3 4] =>     (has some odd?)
  [2 4 6] =not=> (has some odd?))

has looks silly, in that it appears you can do the same with partial:

(fact 
  [2 3 4] =>     (partial some odd?)
  [2 4 6] =not=> (partial some odd?))

That indeed works correctly. There are two reasons to use has instead:

  1. You can use regular expressions, which match according to the rules of extended-equality.

    (fact
      ["ab" "aab" "aaab"] => (has every? #"a+b"))
    
    (fact
      [1 2 3] => (has not-any? #"a+b"))
  2. Some more elaborate uses of partial will not work as you expect:

    user=> (fact [[1] [2]] => (partial every? (just [1])))
    true

    This incorrectly checks out because of how the collection checkers report failures. That's a mistake in Midje's design, one that will be fixed. In the meantime, use has.

Clone this wiki locally