Skip to content

A tutorial introduction for clojure.test users

marick edited this page Feb 9, 2013 · 47 revisions

This tutorial shows a clojure.test user how to migrate to Midje. That migration can be gradual: Midje coexists with clojure.test, so you can use both at the same time, even in the same file.

A sample project

If you want to follow along with this tutorial, you can fetch this project:

% git clone git@github.com:marick/midje-clojure-test-tutorial.git

The project is about a whimsical little function called migrate that "moves" key/value pairs from one map to another. Migrate calls are written left to right: (migrate source-map key-to-migrate destination-map), and the result is the "changed" [source, destination] pair after the migration:

user=> (migrate {:a 1} :a {})
[{} {:a 1}]

You can migrate more than one key:

user=> (migrate {:a 1, :b 2, :c 3} :b :c {})
[{:a 1} {:b 2, :c 3}]

In the case of key clashes, the migration isn't done:

user=> (migrate {:a 1, :b 2} :a :b {:a "not-1"})
[{:a 1} {:b 2, :a "not-1"}]

Tests and facts

migrate has tests. Here are the first two of them:

(deftest migration
  (testing "migration produces two maps with keys (and values) from one moved to the other"
    (is (= [{} {:a 1}]
           (migrate {:a 1} :a {}))))
  (testing "duplicates are not migrated"
    (is (= [{:a "not moved"} {:a "retained"}]
           (migrate {:a "not moved"} :a {:a "retained"})))))

To do the equivalent in Midje, you need to add Midje to your project.clj file:

  :dependencies ...             ;; typically, :dev dependencies
                [midje "1.5.0"]
                ... 

Then use midje.sweet in your namespace:

  (:use clojure.test
        midje.sweet       ;; <<<<  
        migration.core)

Then you can write Midje code that looks structurally very like the clojure.test tests, except for different jargon:

(facts migration
  (fact "migration produces two maps with keys (and values) from one moved to the other"
    (migrate {:a 1} :a {}) => [{} {:a 1}])
  (fact "duplicates are not migrated"
    (migrate {:a "not moved"} :a {:a "retained"}) => [{:a "not moved"} {:a "retained"}]))

The most obvious difference is that the is expressions have been replaced by ones modeled after the way everyone writes examples in documentation: source on the left, some sort of delimiter (like an arrow or newline or comment symbol or repl prompt) to separate the source from the result, and then the result. Flip open a book on Clojure, and I expect you'll see examples like that. The delimiters make them easier to read, and they follow the conventions readers of left-to-right languages expect (particularly that time flows from left to right and top to bottom).

A lot of the structure you see above is optional. For example, here's a minimalist version:

(fact
  (migrate {:a 1} :a {}) => [{} {:a 1}]
  (migrate {:a "not moved"} :a {:a "retained"}) => [{:a "not moved"} {:a "retained"}])

Running tests

When you run the tests with lein tests, midje failures print out:

A midje failure message in lein test output

(I used a screen shot here to emphasize that Midje by default uses terminal colors in its output.)

Unfortunately, Midje's error is not included in the clojure.test's error counts. For that reason, I recommend you use Midje's own Leiningen plugin. To install it, add this to your :user profile in ${HOME}/.lein/profiles.clj:

{:plugins [... [lein-midje "3.0"] ...]}

Now you can do this:

Lein midje output

Notice that both Midje and clojure.test output are reported (and colored so that failures stand out). Both Midje's and clojure.test's failure counts are reported. They are also both used to construct the exit status, which is 0 if there were no problems, a non-zero number otherwise. (Strictly, the exit status is the number of failures, up to 255. So in this case, the exit status is 2.)

Because Clojure and Midje's startup time is slow, you will probably prefer to use "autotest", in which Midje watches your project for changed files. When it sees a change, it reloads the changed files and all files that depend on it. In the following example, I start autotest on a buggy version of migrate, make (and save) a syntax error trying to fix it, and then fix it for real. (I've removed the clojure.test tests to keep the output from flooding the screen.)

Lein midje output

I personally prefer to start autotest from within the repl, using Midje's Repl Tools. That makes it easy to fluidly switch between test-driven development and repl-driven development:

Lein midje output

However, let's move on to what you can do with facts.

Checkers

core_test.clj includes a test that doesn't use is:

  (testing "a rather silly test"
    (is (even? (count (migrate {:a 1} :a {})))))

An equivalent fact looks like this:

(fact
  (count (migrate {:a 1} :a {})) => even?)

When a function appears on the right-hand side of the arrow, the result of the left-hand side is passed to that function. If the function returns a "truthy" value, the fact checks out. Midje comes with a set of predefined checkers.

For example, if we wanted to check only the "destination" part of migrate's output, we could do this:

(fact
  (migrate {:a 1} :a {}) => (contains {:a 1}))

You might not want to use that check it would be fooled by the case where the source, rather than destination, part of the result is {:a 1}. Here's a more precise check that wouldn't be fooled, but still doesn't force you to "complect" together the creation of a result and choosing which pieces to focus on:

(fact
  (migrate {:a 1} :a {}) => (just [irrelevant {:a 1}]))

(just is a checker that insists on a match for every piece of the left-hand-side result. irrelevant matches anything.)

Two other common checkers are truthy and falsey. Consider this clojure.test expression:

(is (not (some even? [1 5])))

The corresponding fact would be this:

(fact
  (some even? [1 5]) => nil)

That works, but it does a poor job of expressing intent. We don't care specifically that the result is nil -- we care that the result counts as false. A better way to translate the code would be with the falsey checker:

(fact
  (some even? [1 5]) => falsey)

Extended equality

A Midje expression like this:

(produce-actual-result) => expected-result)

... is translated into this:

(extended-= (produce-actual-result) expected-result)

As you've seen, expected results that are functions are treated specially. The most important other case of [extended equality] is regular expressions. Consider this fact:

(fact
  (str "the number is " 5) => #"number.*5")

That doesn't mean "check if the resulting string is equal to a regular expression" (which it could never be). Instead, it means "succeed if there's a substring of the result that matches the regular expression".

Tabular tests

Anything else?

If you're a clojure.test user and you'd like this tutorial (or the whole user guide) to cover anything else, send mail to marick@exampler.com.

Clone this wiki locally