Mocking Input in Clojure (Thanks, Colin)
I’m really having a good time building tic-tac-toe in Clojure, but there are definitely still a few snags with learning a new language- especially a functional one. One of these has been properly mocking out functions for tests.
Fortunately, Clojure has the dangerously powerful with-redefs
function, which both Ginny and Eric have written about. I say dangerously powerful because it’s very simple to redefine any function you want. That makes it easy to have a test passing that might not even really test anything (which I suppose is true with many mocks and stubs).
I was able to use with-redefs
to mock out a single read-line
call that gets input from a user and tests a few of my console UI functions. Here’s a simple example of getting a move.
(it "should get a move from a human-player"
(with-redefs [read-line (constantly "1")]
(should= 1 (get-human-move))))
However, I was hitting a wall when trying to put together a series of inputs that could test behavior for invalid input or even an entire game loop.
If I learned one thing during my last foray into tic-tac-toe it’s that there is “the struggle” that’s somewhat necessary to help things sink in, and then there’s the useless banging of the head against a wall. I spent a couple of days thinking about how to simulate the input with no real solutions, so before I spent anytime engaging in the latter activity I decided to take a peek at Colin’s test file for his tic-tac-toe implementation in Clojure, where I discovered not one, but two, gems.
First up is the with-in-str
function. It’s part of the Clojure core but I hadn’t checked it out yet. This function does a very similar thing to what I was doing with redefining read-line
, but as soon as I saw it knew that this is where I would pass a sequence of inputs. And that’s when I saw the second gem: Colin created a function called make-input
that created a lazy sequence of inputs that could be passed to with-in-str
.
(defn make-input [coll]
(apply str (interleave coll (repat "\n"))))
The interleave
function is the key here, as it creates the lazy sequence, in this case placing a “\n” between each item in the collection that you pass it. So, if I want to test that my get-human-move
rejects words and gets only valid numbers I can do this:
(it "shoud reject a non number and get another input"
(with-in-str (make-input '("nine" 3))
(should= 3 (get-human-move))))
Lazy sequences are definitely something I’m working on wrapping my brain around so the discovery of Colin’s make-input
method was a sweet two-for.
(Thanks as well to Ginny for tipping me off to the ClojureDocs Clojure Core page. For the past month I’ve had at least one browser tab always open to it.)