user524824
user524824

Reputation:

clojure: elegant mocking of functions with different values

While writing unit tests I have to sometimes mock a function to return a sequence of defined values for each function call.

Currently I am doing something like this:

(testing "fitness-proportionate selection"
  ; store sequence of rand values in atom, first val is ignored as we always use rest
  ; normalized fitness: 0: 0 1: 1/6 2: 1/3 3: 1/2
  ; summed fitness 0: 0 1: 1/6 2: 1/2 3: 1
  (let [r (atom [0 1 1/2 1/6 0])] (with-redefs [rand (fn [] (first (swap! r rest)))]
    (is (= [3 2 1 0] (fitness-proportionate-selection [0 1 2 3] identity))))))

Can somebody help me to find a more elegant way to do this? Something that is more readable, and contains less logic. Which would result in less errors in the unit tests itself. I am currently using clojure.test and would prefer to not use an additional library.

Upvotes: 1

Views: 698

Answers (1)

juan.facorro
juan.facorro

Reputation: 9930

I couldn't think of an alternate way of mocking the rand function aside from having some kind of reference holding the sequence of values it should return. This makes sense since rand is itself a function that generates its values from some other (pseudo-random) source.

That said, I would create a higher-order function that returns a number generator based on a sequence of numbers, instead of embedding that logic in the test code.

(defn gen-rand
  "Returns a no args function that mocks `rand`,
  which returns on each call a number from s in 
  the same order provided."
  [s]
  (let [x (atom s)]
    #(let [n (first @x)] 
       (swap! x rest)
       n)))

(defn fitness-proportionate-selection
  "Mock function."
  [s f]
  (vec (repeatedly 4 rand)))

(testing "fitness-proportionate selection"
  (with-redefs [rand (gen-rand [1 1/2 1/6 0])]
    (is (= [1 1/2 1/6 0] (fitness-proportionate-selection [0 1 2 3] identity)))))

Note that I changed the code so that all values in the sequence provided to gen-rand are returned and the first one is not discarded.

Upvotes: 3

Related Questions