简体   繁体   中英

`with-redefs` not binding certain functions (Clojure)

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.

Here is my test

(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])))))))

Here are my function definitions in 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.

A different test that works 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM