Search code examples
unit-testingclojuremockingtddmidje

Clojure Unit testing : Check if a function was called


I'm trying to write some unit tests for my clojure function (I'm using clojure.test, but I can switch to midje if necessary).

I have a function that reads like :

(defn GenerateNodes
   [is-sky-blue? hot-outside? name]
   (cond
     (is-sky-blue? name) (generate-sky-nodes)
     (hot-outside?) (generate-hot-nodes)))

when unit testing this function, I want to write the following test case :

(deftest when-sky-blue-then-generate-sky-nodes
   (let [is-sky-blue true]
       (GenerateNodes (fn[x] println "sky nodes generated."))
          (is (= true Was-generate-hot-nodes-called?))

How can I assert that the function generate-sky-nodes was called ? or not ? I would use a mocking framework in C# or java, but I don't know about clojure.


Solution

  • What you have already is not far from a working functional version. I changed things around a bit to be more idiomatic to Clojure.

    The following assumes that generate-sky-nodes and generate-hot-nodes each return some value (this can be done in addition to any side effects they have), i.e.:

    (defn generate-sky-nodes
       []
       (doseq [i (range 10)] (do-make-sky-node i))
       :sky-nodes)
    

    then, your generate-nodes is adjusted as follows:

    (defn generate-nodes
      [sky-blue? hot-outside? name]
      (cond
       (sky-blue? name) (generate-sky-nodes)
       (hot-outside?) (generate-hot-nodes)))
    

    and finally, the functional version of the tests:

    (deftest when-sky-blue-then-generate-sky-nodes
      (let [truthy (constantly true)
            falsey (constantly false)
            name nil]
      (is (= (generate-nodes truthy falsey name)
             :sky-nodes))
      (is (= (generate-nodes truthy truthy name)
             :sky-nodes))
      (is (not (= (generate-nodes falsey falsey name)
                  :sky-nodes)))
      (is (not (= (generate-nodes falsey truthy name)
                  :sky-nodes)))))
    

    The general idea is that you don't test what it did, you test what it returns. Then you arrange your code such that (whenever possible) all that matters about a function call is what it returns.

    An additional suggestion is to minimize the number of places where side effects happen by using generate-sky-nodes and generate-hot-nodes to return the side effect to be carried out:

    (defn generate-sky-nodes
       []
       (fn []
        (doseq [i (range 10)] (do-make-sky-node i))
        :sky-nodes))
    

    and your call of generate-nodes would look like the following:

    (apply (generate-nodes blue-test hot-test name) [])
    

    or more succinctly (though admittedly odd if you are less familiar with Clojure):

    ((generate-nodes blue-test hot-test name))
    

    (mutatis mutandis in the above test code the tests will work with this version as well)