Setup and teardown

Conan edited this page May 28, 2015 · 14 revisions
Clone this wiki locally

Executable examples

Testing code that works with mutable state often requires that you change that state before or after a fact or checkable. 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.

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

:facts could be replaced with :contents, in which case the atom would be set to 0 once before all the facts in the scope of the expression.

Consider this a historical legacy. The older mechanism allowed you to wrap setup and teardown around individual checkables, but I now think it's better to do that by putting each such checkable in its own nested fact.

Multiple forms

Annoyingly, before only accepts a single form (like the "then" part of an if, rather than like a when body). 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:

(with-state-changes [(before :facts (println "outer setup"))]
  (fact "outer"
    (println "starting outer fact")
    (with-state-changes [(before :facts (println "  inner setup"))]
      (println "  going to check inner fact")
      (fact "inner"
        (println "  Here I am in the inner fact"))
      (println "  done checking inner fact")
    (println "finishing outer fact")))

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.

Around

If you need code executed around facts, use around:

(with-state-changes [(around :facts (transaction ?form (rollback)))]
  (fact ...)
  (fact ...))

Importantly note the ?form which represents the body of the wrapped facts.

Around can be useful for example for wrapping your tests in a database transaction and rolling back to keep a clean database.

Like before, around takes only one form to evaluate.

Repl and whole-file 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. namespace-state-changes can be used to establish global (namespace-specific) setup and teardown for a repl session. Or, if you dislike big wrapping forms, you can put it at the beginning of a namespace to affect all facts in the file.

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=> (namespace-state-changes [(before :facts (reset! state 0))])
user=> (fact "uses a function to update the current value"
         (swap! state inc)
         @state => 1)

Notice that namespace-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. You can also leave off the square brackets, which I'll do in the following.

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

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

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

You can completely undo a namespace-state-changes like this:

(namespace-state-changes)