Setup and teardown
Clone this wiki locally
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.
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.
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
(with-state-changes [(before :facts (do (println "Before code") (reset! state inc)))] (fact...))
Sorry about that.
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.)
If you need code executed after facts, use
(with-state-changes [(after :facts (swap! state dec))] (fact ...) (fact ...))
after takes only one form to evaluate.
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.
If you need code executed around facts, use
(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.
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?
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
(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)
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: