How to define prerequisite functions in Midje?

248 views Asked by At

I should start by saying I'm new to Clojure, and FP in general. I've been reading the documentation about how to define prerequisites in Midje, but I can't make sense of some of it.

My understanding was that to do top-down TDD, you're supposed to start by writing a test in your test module, with an unfinished statement at the top that 'declares' all the prerequisite functions you haven't defined yet. Then you can fiddle with those prerequisite functions in the provided function within your test (describing their return values and such).

My confusion lies in how you're supposed to get your actual source module to recognize the prerequisite functions. Here's a very simple and contrived example I'll use to illustrate my meaning:

    ;;; in my run_game_test module

    (ns clojure-ttt.run-game-test
      (:require [midje.sweet :refer :all]
                [clojure-ttt.run-game :refer [start-game]]))

    (unfinished do-turns)

    (fact "`start-game` returns whatever `do-turns` returns"
      (start-game) => ..winner..
      (provided
        (do-turns) => ..winner..))

And then to make the test fail properly, I just write a stub of a function in my run_game module.

    (ns clojure-ttt.run-game)

    (defn start-game
      [])

So far so good. I run the tests and they fail because:
a) do-turns isn't getting called
b) start-game isn't returning anything.

So now to make the test pass, by changing start-game to call and return (do-turns). For the record, do-turns is a hypothetical prerequisite function that I'm going to get from a not-yet-existing module--which, as I understand, is how top-down TDD works.

    (defn start-game
      []
      (do-turns))

Now, understandably, I get a huge error; Clojure can't resolve the symbol do-turns. So I thought, maybe if I (declare do-turns) at the top I could keep it from blowing up. No, I get a different error because I'm trying to call an unbound function.

I tried a couple other ways of getting Clojure to recognize do-turns but it seems like the unfinished statement is giving it issues. Am I just using unfinished wrong?

1

There are 1 answers

2
user2609980 On BEST ANSWER

From the Midje docs:

unfinished is similar to Clojure's declare in that it can take multiple arguments and define a var for each one of them. Unlike declare, it binds the var to a function that blows up if called

So when you do (unfinished do-turns) and then call it with (do-turns) an exception is thrown:

Error #'do-turns [the var] has no implementation, but it was called like this: (do-turns) midje.util.exceptions/user-error (exceptions.clj:13)

You can fix this by removing the (unfinished do-turns) part and provide an implementation of do-turns. E.g.,

(ns clojure-ttt.run-game)

(defn do-turns)
  [])

(defn start-game
  []
  (do-turns))

and refer it in your test

(ns clojure-ttt.run-game-test
  (:require [midje.sweet :refer :all]
            [clojure-ttt.run-game :refer [start-game do-turns]])) ; <- do-turns

;; Remove `(unfinished do-turns)` since it is no longer unfinished

(fact "`start-game` returns whatever `do-turns` returns"
  (start-game) => ..winner..
  (provided
   (do-turns) => ..winner..)) 
;; => returns true

Note that when you have provided an implementation of do-turns, (unfinished do-turns) will throw an exception since an implementation already exists, so it is removed.

Now you have shown that a call to (start-game) returns what (do-turns) returns.