Skip to content

Establishing fact wide prerequisites

marick edited this page Feb 28, 2013 · 7 revisions

It's often the case that you have prerequisites that you want to apply to many checkables. As a simple example, consider this:

(fact
  (function-under-test 1 1) => 3
  (provided
    (x-handler 1) => 1                
    (y-handler 1) => 2)               ;; 1

  (function-under-test 8 1) => 1
  (provided
    (x-handler 8) => -1               
    (y-handler 1) => 2))              ;; 1

Look at the lines marked "1". They're identical. As such, they distract from what's special about each checkable. So let's extract them so that they apply to all checkables in the fact:

(fact
  (prerequisite (y-handler 1) => 2)     ;; <<== new

  (function-under-test 1 1) => 3
  (provided
    (x-handler 1) => 1)
    
  (function-under-test 8 1) => 1
  (provided
    (x-handler 8) => -1))

That helps make it clear that all the checkables in this fact depend on a particular property of y-handler.

You can have more than one fact-wide prerequisite. For example, you might gather all the prerequisites together at the top of the fact:

(fact
  (prerequisites (y-handler 1) => 2
                 (x-handler 1) => 1     ;; <<== new
                 (x-handler 8) => -1)   ;; <<== new

  (function-under-test 1 1) => 3
  (function-under-test 8 1) => 1)

(I probably wouldn't do that: it obscures the connection between the the first argument to function-under-test and the single argument to x-handler.)

Note that you can use either prerequisite or prerequisites. We at MidjeCo are very respectful of grammatical number.

Fact-wide prerequisites do not have to be used

When you create a prerequisite with provided, you predict it will be called at least once. If not, Midje will signal a failure. Prerequisites created with prerequisites do not need to be called. To see why, consider a flight checklist. The plane can take off only if all of the pilot, copilot, and engines are ready:

(fact
  (prerequisites (pilot-ready? ..flight..) => true
                 (copilot-ready? ..flight..) => true
                 (engines-ready? ..flight..) => true)
  
  (flight-ready? ..flight..) => truthy
  (flight-ready? ..flight..) => falsey (provided (pilot-ready? ..flight..) => falsey)
  (flight-ready? ..flight..) => falsey (provided (copilot-ready? ..flight..) => falsey)
  (flight-ready? ..flight..) => falsey (provided (engines-ready? ..flight..) => falsey))

Suppose flight-ready? is implemented like this:

(def flight-ready? (every-pred pilot-ready? copilot-ready? engines-ready?))

In the second checkable, pilot-ready? returns false. That means every-pred won't check the remaining two predicates. It would be annoying if Midje complained they hadn't been called.

Nesting and ordering

Prerequisites apply to any facts included in the fact where they're defined.

(fact "prerequisites can be nested"
  (prerequisite (x-handler 1) => 8000)

  (fact 
    (prerequisite (y-handler 1) => 80)
    (function-under-test 1 1) => 8080)

  (fact
    (prerequisite (y-handler 1) => -8000)
    (function-under-test 1 1) => 0))

When there are matches for a prerequisite at different nesting levels, the innermost takes precedence. provided prerequisites override any from a prerequisites form.

(fact "prerequisites can be nested"
  (prerequisites (x-handler 1) => 10
                 (y-handler 1) => 8)
  (fact 
    (prerequisite (y-handler 1) => 33)

    (function-under-test 1 1) => (+ 10 33)

    (function-under-test 1 1) => (+ 10 99)
    (provided
      (y-handler 1) => 99)))

When more than one prerequisite at the same nesting level match, it's the latest one that's chosen. That allows for "catch all" or default prerequisites:

(fact "catch-all or default prerequisites"
  (prerequisites (x-handler anything) => 1
                 (y-handler anything) => 2
                 (y-handler 3) => 333)
  (function-under-test 1 1) => (+ 1 2)
  (function-under-test 1 3) => (+ 1 333))

Note that the match is not the most specific. If the order of y-handler prerequisites were switched, y-handler would always return 2.

Lexical scoping

prerequisite expressions have access to lexically-scoped symbols:

(let [my-favorite-argument-value 1
      my-favorite-expected-value 32000]
  (fact "lexical scoping is obeyed"
    (prerequisites (x-handler my-favorite-argument-value) => my-favorite-expected-value
                   (y-handler my-favorite-argument-value) => my-favorite-expected-value)

    (function-under-test my-favorite-argument-value my-favorite-argument-value)
    => (* 2 my-favorite-expected-value)))

There may conceivably be a use for that.

Clone this wiki locally