I'm trying to test a "tic tac toe" command line game in Clojure, and want to redefine functions that use read-line
to mock user input. However, with-redefs
is not working as I expect, and also (seemingly) differently from other tests.
(ns game.setup-test
(:require [clojure.test :refer :all]
[clojure.string :as st]
[game.helpers :as gh]
[game.setup :refer :all]))
(deftest initial-setup-test
(testing "Can set initial options"
(with-redefs [get-game-type-input (fn [] "1")
get-marker-input (fn [] "X")
get-starter-input (fn [] "1")]
(do (initial-setup my-game)
(is (= :human (get-in @my-game [:player1 :type])))
(is (= :human (get-in @my-game [:player2 :type])))
(is (= [:player1 :player2] (get @my-game :up-next)))
(is (= "X" (get-in @my-game [:player1 :marker])))
(is (= "O" (get-in @my-game [:player2 :marker])))))))
setup.clj
(ns game.setup)
(defn get-input [requestor validator]
(fn [] (do (requestor)
(if-let [input (validator (read-line))]
input
(recur)))))
(def get-marker-input (get-input request-p1-marker valid-marker?))
(def get-game-type-input (get-input request-game-type valid-type?))
(def get-starter-input (get-input request-starter-type valid-starter?))
(def set-starter
(partial set-game-option
get-starter-input
starter-opts
swap-merge
print-starter-player))
(defn initial-setup [game-state]
(do (set-game-type game-state)
(set-player-markers game-state)
(set-starter game-state)))
(defn set-game-option [input-getter attr-opts updater printer game]
(-> (input-getter) ; gets user input
attr-opts ; returns corresponding update-map
(#(updater game %)) ; updates the game-state with update-map
printer)) ; prints the updated game-state
When running this test. I still see the STDOUT output from the request-*
function, and the test waits for input to STDIN. So, I assume that with-redefs
is not "re-defing" the get-*
functions as I expect.
(deftest get-marker-input-test
(testing "Reads perfect input"
(with-redefs [read-line (fn [] "X")]
(is (= "X" (get-marker-input))))))
Any help is appreciated! :)
The reason you aren't seeing your redef take effect is that the function under test set-starter
via initial-setup
uses partial
. partial
returns a new function that retains the reference to the previously defined function.
A simpler example:
boot.user=> (def my-dumb-fn (partial str "prefix"))
#'boot.user/my-dumb-fn
boot.user=> (with-redefs [str (fn [s] "hello")] (str 23))
"hello"
boot.user=> (with-redefs [str (fn [s] "hello")] (my-dumb-fn 23))
"prefix23"
Try to use not read-line
but just read
: it reads the input string into data structures rather than strings. More over, when it finds a space (or the end of reading form, saying more precisely), it stops reading the input preventing the rest of it of being consumed.
A special built-in macro with-in-str
allows you to specify some certain input for testing purposes as follows:
(with-in-str "X 0 X 0 0 X"
(let [turn1 (read)
turn2 (read)
;; more reads ...
]
[turn1
turn2
;; more read values
]))
Returns
[X 0]
In other words, there is no need to redefine functions.
As @dpassen says, don't use partial
(I never do). Here is an alternative:
> lein new tupelo-app fred ; make a new project `fred` with nice defaults
> cd fred
Cut & paste so looks like this:
~/expr/fred > cat src/tst/fred/core.clj
(ns tst.fred.core
(:use fred.core tupelo.core tupelo.test)
(:require
[clojure.java.io :as io]
[tupelo.string :as ts] )
(:import [fred Calc]))
(def x 5)
(def p1 (partial + x))
(defn p2 [y] (+ x y))
(dotest
(spyx (p1 2))
(spyx (p2 2))
(with-redefs [x 10]
(spyx (p1 3))
(spyx (p2 3))))
and we get results:
---------------------------------
Clojure 1.9.0 Java 9.0.1
---------------------------------
Testing fred.core
Testing tst.fred.core
(p1 2) => 7
(p2 2) => 7
(p1 3) => 8
(p2 3) => 13
So, by defining p2
as a function (instead of a "constant" var
with partial
), we get the expected result and with-redefs
works correctly.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.