Skip to content
marick edited this page Feb 17, 2013 · 7 revisions

Testing code that works with mutable state often requires that you change that state before or after a fact or individual prediction. Midje has two ways to do that, one baroque and deprecated, one simpler and encouraged. It's my hope that the simpler way will suit the vast majority of needs. If not, [[let me know|mailto:marick@exampler.com]].

For now, the simpler mechanism is built on top of the baroque one. Because of that, some oddities leak through into the syntax.

Setup

As an example of state, we'll use an atom:

(def state (atom nil))

Here is an expression that sets the atom to 0 before every fact in its scope:

(with-state-changes [(before :facts (reset! state 0))]
  (fact ...)
  (fact ...)
  ...)

In this simpler mechanism, :facts is the only keyword that can appear in its spot. Consider it a historical legacy. The older mechanism allowed you to wrap setup and teardown around individual predictions, but I now think it's better to do that by putting each such prediction in its own fact.

Multiple forms

Annoyingly, before only accepts a single form. If you give more than one, you'll get an error message something like this:

Midje could not understand something you wrote: 
  `(before :facts (println "Before code") (reset! state inc))`.
  `before` has two forms: `(before <target> <form>)` and `(before <target> <form> :after <form>).

To have multiple forms, you'll have to use do:

(with-state-changes [(before :facts (do (println "Before code")
                                        (reset! state inc)))])
   (fact...))

Sorry about that.

Nesting

If nested facts both have before forms, they are evaluated in outside-in order for each nested fact. Consider the following:

(facts swap!
  (with-state-changes [(before :facts (reset! state 0))]
    (fact "uses a function to update the current value"
      (swap! state inc)
      @state => 1)

    (fact "that function can take additional args"
      (swap! state - 33)
      @state => -33)

    (fact "swap returns the new value"
      (swap! state inc) => 1)))

The output is this:

outer setup
starting outer fact
  going to check inner fact
outer setup                   ;; note that the outer setup gets done *again* for the inner fact
  inner setup
  here I am in the inner fact
  done checking inner fact
finishing outer fact

This is likely The Wrong Thing, and it will likely be phased out in the future. Avoid depending on it. (The easiest way to do that is make your setup code idempotent, meaning that it sets the state the same way no matter how many times it's called. With an atom (reset! state 0) is idempotent, whereas (swap state dec) is not.

Teardown

If you need code executed after facts, use after:

(with-state-changes [(after :facts (swap! state dec))]
  (fact ...)
  (fact ...))

Like before, after takes only one form to evaluate.

Ordering

Nested after expressions are executed in inside-out order, the opposite of before. Consider the following:

(with-state-changes [(before :facts (println "outer in"))
                     (after :facts (println "outer out"))]
  (with-state-changes [(before :facts (println "  inner in"))
                       (after :facts (println "  inner out"))]
    (fact (+ 1 1) => 2)))

Here's what's printed:

outer in
  inner in
  inner out
outer out

Teardown code is executed even if the body of a fact throws an exception. However, it is not executed if a corresponding before throws an exception. For example, in this case:

(with-state-changes [(before :facts (throw (new Error)))
                     (after :facts (println "after"))]
  (fact ...))

... "after" will not be printed. The deprecated version of with-state-changes has a form that catches errors in the setup part.

Repl support

Some people using Midje from their editor or IDE use special keypresses that send the current fact to the repl and handle its results. That's awkward if you have a with-state-changes surrounding 30 facts, 29 of which you don't care about. Do you really have to send that entire 30-fact form to the repl, just to get the setup or teardown for the one fact you care about?

Not necessarily. repl-state-changes can be used to establish global (namespace-specific) setup and teardown for a repl session. To see how that works, consider again the earlier example of testing swap!:

(facts swap!
  (with-state-changes [(before :facts (reset! state 0))]
    (fact "uses a function to update the current value"
      (swap! state inc)
      @state => 1)

    (fact "that function can take additional args"
      (swap! state - 33)
      @state => -33)

    (fact "swap returns the new value"
      (swap! state inc) => 1)))

The first fact can be tested in isolation like this:

user=> (repl-state-changes [(before :facts (reset! state 0))])
user=> (fact "uses a function to update the current value"
         (swap! state inc)
         @state => 1)

Notice that repl-state-changes has the same syntax as with-state-changes, except there's no form after the vector. That makes it easy to copy and paste a with-state-changes into the repl.

Any new use of repl-state-changes erases the effect of the previous use. Consider this:

user=> (repl-state-changes [(before :facts (println "before"))])
user=> (repl-state-changes [(after :facts (println "after"))])
user=> (fact (+ 1 1) => 2)

Only "after" will be printed when the fact is checked.

That implies that you can (in effect) undo a repl-state-changes like this:

(repl-state-changes [(after :facts identity)])
Clone this wiki locally