-
Notifications
You must be signed in to change notification settings - Fork 129
Setup and teardown
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.
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.
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 after
:
(with-state-changes [(after :facts (swap! state dec))]
(fact ...)
(fact ...))
Like before
, after
takes only one form to evaluate.
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.
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.
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)