13

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.

4
  • 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). Commented Aug 20, 2013 at 8: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. Commented Aug 20, 2013 at 9: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. Commented Aug 20, 2013 at 9: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. Commented Aug 20, 2013 at 15:49

4 Answers 4

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)

Sign up to request clarification or add additional context in comments.

2 Comments

This is a great example for someone who is not yet familiar with the functional paradigm. +1
Great post! Is there a name for this pattern whereby side-effecting functions return a "token" that aids in testing?
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")))

1 Comment

Just a warning: This library has not been updated since 2012 and does not appear to be compatible with Clojure 1.9.
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)))

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.