Skip to content
marick edited this page Nov 2, 2010 · 18 revisions

Suppose I'm writing a Clojure version of Ruby's Sequel RDBMS facade, and it's time to make INSERT work. Here's a first test:

  (fact
    (-> (datastate :countries) (insert {:region "North America" :name "USA"})) => anything
    (datastate :countries) => (SOME-CHECKER {:region "North America", :name "USA"}))

I want the as-yet unwritten checker to check the entire :countries "datastate" (table) against its argument, except that it should ignore unmentioned columns. (For this test, I don't care about the :id column.)

Astoundingly enough, there's a built-in checker that's almost what I want. It's called only-maps-containing. The name is not great. Suggestions for a better one? There's also a maps-containing which allows both extra keys and extra maps. This one insists that there be no maps not mentioned in the expected result. There's a problem using it, though. A datastate isn't the contents of the table. Think of it as an as-yet-unexecuted query. To execute the query, I can call all on the datastate. Like this:

    (all (datastate :countries)) => (only-maps-containing {:region "North America", :name "USA"}))

That works, but I'm going to have a zillion tests that use all, and I'd rather bury that away in a helper function. That is, in this:

  (datastate :countries) => (rows-matching {:region "North America", :name "USA"}))

... where rows-matching looks like this:

(defn rows-matching [& expected-rows]
   (fn [actual-datastate]
      ( (only-maps-containing expected-rows) (all actual-datastate))))

That works fine when the test passes. But here's what gets printed when it fails:

FAIL at (write.clj:41)
Actual result did not agree with the checking function.
    Actual result: {:table :countries}
Checking function: (rows-matching {:region "North America", :name "USA"})
FAILURE: 1 fact was not confirmed. (But 1 was.)

This isn't such a helpful message. We don't want to see dataset, we want to see (all dataset). Chatty checkers make this possible. Just change the fn in the rows-matching definition to chatty-checker, like this:

(defn rows-matching [& expected-rows]
   (chatty-checker [actual-datastate]
      ( (only-maps-containing expected-rows) (all actual-datastate))))

Now we get this kind of error message:

FAIL at (write.clj:41)
Actual result did not agree with the checking function.
    Actual result: {:table :countries}
Checking function: (rows-matching {:region "North America", :name "USA"})
During checking, these intermediate values were seen:
   (all actual-datastate) => []

Details

  • Chatty checkers only print the results of top-level functions. That is, you'll get two lines of output from the following. The first will show the result of (f 1) and the second will show (f (g 1)). *my-special-value* and (g 23) won't be shown. I'm hoping that strikes a decent balance between the annoyance of noisy output and the annoyance of not seeing a value you need. (And the implementation's easier this way.) Let me know.

     (chatty-checker [actual]
         (truth-valued-function *my-special-value* (f 1) (f (g 23))))
    
Clone this wiki locally