12

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.

omiel
  • 1,573
  • 13
  • 16
Attilah
  • 17,632
  • 38
  • 139
  • 202
  • 4
    Good question. It seems to me you're trying to apply imperative style testing to declarative code. You're not supposed to describe *how* things work, but *what* they do, so a function being called is IMO an irrelevant detail. To be confirmed by functional programming experts though (which I'm not). – guillaume31 Aug 20 '13 at 08:02
  • 1
    @guillaume31, I think this is the difference between mocks and stubs. Stubs are there just to provide fake implementations of supporting behavior, while mocks do that and also do accounting. Personally I find mocks to be exceptionally bad idea, even in OO world. Doubly so in functional world. But it may be just me. – ivant Aug 20 '13 at 09:06
  • @ivant Dunno. I guess stubs still somehow describe the *how*, although you probably can't do without them to get performant tests. Mocks I personally do find useful, not to do micro-accounting, but to verify that an object doesn't talk rude (i.e. outside protocol) to one of its peers, making for the lack of fluent enforcement of these protocols in OO typesystems. – guillaume31 Aug 20 '13 at 09:51
  • 1
    @guillaume31, I agree it seems to me like it would make more sense (in a functional world) to consider the function as a black box and just test what it returns. but there are cases when all the function does is a side effect and you want to make sure it effected the world in a proper way. – Attilah Aug 20 '13 at 15:49

4 Answers4

11

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)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
noisesmith
  • 20,076
  • 2
  • 41
  • 49
  • This is a great example for someone who is not yet familiar with the functional paradigm. +1 – Adam Arold Aug 21 '13 at 14:58
  • Great post! Is there a name for this pattern whereby side-effecting functions return a "token" that aids in testing? – camdez Sep 30 '13 at 03:06
9

You can write a Macro yourself to mock functions and check whether a function got called or not. Or you can use expect-call library.

(defn check-error [a b]
  (when (= a :bad-val)
  (log :error b)))

; This will pass
(deftest check-logging
  (with-expect-call (log [:error _])
  (check-error :bad-val "abc")))

; This will fail
(deftest check-logging-2
  (expect-call (log [:error _])
  (check-error :good-val "abc")))
Chiron
  • 20,081
  • 17
  • 81
  • 133
4

Using Midje's checkables:

(unfinished is-sky-blue? hot-outside?)
(facts "about GenerateNodes"
  (fact "when the sky is blue then sky nodes are generated"
    (GenerateNodes is-sky-blue? hot-outside? ..name..) => ..sky-nodes..
    (provided
      (is-sky-blue? ..name..) => true
      (generate-sky-nodes)    => ..sky-nodes..
      (generate-hot-nodes)    => irrelevant :times 0)))
omiel
  • 1,573
  • 13
  • 16
0

You can use mock-clj.

(require ['mock-clj.core :as 'm])

(deftest when-sky-blue-then-generate-sky-nodes
  (m/with-mock [is-sky-blue? true
                generate-sky-nodes nil]
    (GenerateNodes (fn[x] println "sky nodes generated.") ... ...)
    (is (m/called? generate-sky-nodes))))
Ming
  • 4,110
  • 1
  • 29
  • 33