Skip to content

Setup, Teardown, and State

marick edited this page Apr 25, 2011 · 11 revisions

This description is valid for Midje 0.8.1 and following.

The short version

Read thing-wrapped as :checks, :facts, or :contents. Note these are all keys. They're explained below.

A wrapper is one of the following:

  • (around thing-wrapped (... ?form ...))

    Whenever the thing-wrapped is seen, it is substituted for the ?form. Note the leading ? --- it's required.

  • (before thing-wrapped ( code ) :after ( code ))

    The two code blocks are executed before and after the thing-wrapped. The :after and what follows can be omitted. The :after code is executed even if the thing-wrapped throws an exception.

  • (after thing-wrapped ( code ))

    The code block is executed after the thing-wrapped, even if an exception was thrown. Nothing's done before it.

Zero or more wrappers can be found in three different forms:

  • (against-background [ wrappers ] ...)

    The wrappers apply to all the forms within the against-background.

  • (fact ... (against-background wrappers ) ...)

    Semantically, this is the same as an against-background that wraps this single fact. The against-background form can appear anywhere in a fact's top-level forms, and there can be more than one. Note that it's not surrounded by [].

  • (background wrappers ...)

    The wrappers take effect immediately and apply until they're erased by another background.

The following shows what different things-wrapped mean:

     (against-background [ (before :contents (A)) (before :facts (B)) (before :checks (C)) ]
        ; A is evaluated here.
        ; B is evaluated here
        (fact 
            ; C is evaluated here
            (f) => 1
            ; C is evaluated here
            (g) => 2)
       
        ; B is evaluated here
        (fact 
           ; C is evaluated here
           (f) => 1))

The longer version

The following defines setup for each fact that follows it in the file. An atom is initialized to zero, and a database connection is made available in a local variable.

     (background (before :facts (swap! my-atom (constantly 0)))
                        (around :facts (sql/with-connection db ?form)))

The same can be done to a selected group of facts by wrapping them with against-background:

     (against-background [ (before :facts (swap! my-atom (constantly 0)))
                                     (around :facts (sql/with-connection db ?form))) ]
         (fact...)
         (fact ....)

An against-background can also be placed within a single fact:

     (fact 
        (swap! my-atom inc) => 1
        (swap! my-atom inc) => 2
        (against-background (before :facts (swap! my-atom (constantly 0)))))

This means the same thing as an against-background that surrounds the fact. It's convenient if you use magical keypresses to send a whole fact to a REPL to be evaluated. You can put the against-background at the start of the fact or the end (or the middle): wherever you think is clearest.

Notice in the above that the before code only executes once, at the beginning of the fact. The state change made by the first check is retained at the start of the second. You can also have code that executes before each of the checks:

     (fact 
        (swap! my-atom inc) => 1
        (swap! my-atom dec) => -1
        (against-background (before :checks (swap! my-atom (constantly 0)))))

before takes another form:

      (before :checks (swap! my-atom (constantly 0)) :after (swap! my-atom (constantly 33)))

The after code executes no matter if a check fails or an exception is thrown "through it".

If you only want code to execute after a :check or :fact, use this:

      (after :checks (... code ....))

In addition to :facts and :checks, you can also use :contents. Suppose you want a database connection to be established for several facts, not for each of them separately. That would be done like this:

     (against-background [(around :contents  (sql/with-connection db ?form))]
          (fact ...)
          (fact ...)
          (fact....))

You can also use :contents in a background that applies to the whole file, like this:

    (background (before :contents (...code...)))

This is exactly the same thing as executing the code directly, except that the execution doesn't happen if you're in production mode.

Access to lexical scope

You can think of all of this as doing a kind of macroexpansion. Consider this setup:

      (background (around :facts (let [x 1] ?form))
                         (around :checks (let [y (+ x 2)] ?form)))
      (fact (+ x y) => 3))

This means the same thing as the following:

      (let [x 1]
         (fact
            (let [y (+ x 2)] 
               (+ x y) => 3))

Variables visible at the point the background is defined are not visible when it's used to expand a fact. The following will not work:

     (let [x 1]
        (background (around :fact (swap! my-atom (constantly x))))
     ...
     (fact @my-atom => 1)

Limited expansion

I said above that background expansion is like macroexpansion. Only Midje-related macros are expanded (most notably, fact). Other macros are left alone. That's so that any side-effects of their expansion don't happen until the whole body of code is executed. That's because of...

deftest

The following works:

      (background (around :facts (sql/with-connection db ?form))
                         (before :facts (create-country-table) :after (sql/drop-table :countries)))

      (deftest different-types-of-insertion
         (fact "easiest for typing"
            (-> (datastate :countries) (insert :region "North America" :name "USA"))
            (datastate :countries) => (rows-matching {:region "North America", :name "USA"})))
            
         (fact "maps are easier for programs"
            (-> (datastate :countries) (insert { :region "North America", :name "USA"}))
            (datastate :countries) => (rows-matching {:region "North America", :name "USA"})))

It works even though deftest's macroexpansion actually just shoves its body - unexpanded - into metadata, to be expanded later.

Default prerequisite facts

Since both [[default prerequisite facts|Background-prerequisites] and state wrappers use background and against-background, they can be mixed:

      (background (around :facts (sql/with-connection db ?form))
                         (gerund ...verb...) => 33)

If you want to play games with background prerequisites referring to variables set up by wrappers, consider the background prerequisites to be scoped with :checks

Clone this wiki locally