-
Notifications
You must be signed in to change notification settings - Fork 0
Setup and teardown
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.
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.
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.
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.
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.
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)])