Clojure Geek

Writing about learning Clojure

Testing Private Functions in Clojure

Unit Testing private functions/methods is controversial because you should test the public api and if the public api calls the private functions/methods then you are golden. However, I often like to test private methods/functions when developing. Sometimes I remove those after I have written a more comprehensive test later.

First, lets talk about how do you define a private function? I found three ways:

1
2
3
4
5
(def- my-private-function)

(def ^:private my-private-function)

(def ^{:private true} my-private-function)

Curious, I looked at the source of defn-

1
2
3
4
5
6
(defmacro defn-
  "same as defn, yielding non-public def"
  {:added "1.0"}
  [name & decls]
    (list* `defn (with-meta name (assoc (meta name) :private true)) decls))
nil

The first is just a macro that adds the metadata to the function, same as if you added the metadata yourself. However I like the second version ^:private, it is clear when reading the code.

Testing a private function in clojure is not so obvious at first:

The function I want to test:

1
2
3
4
(defn ^:private prepare-url [config url]
  (if (str/starts-with? url "http")
    url
    (str (:host config) url)))

To call the private function outside of the namespace where it is defined, you call it with the reader macro like this:

1
#'namespace/my-private-function

How I wrote the test with the private function:

1
2
3
4
5
6
7
8
(def test-config {:host "http://localhost:1111"})

(deftest test-prepare-url
  (let [private-function #'hawk/prepare-url]
    (is (= "http://localhost:1111/api/users" 
           (private-function test-config "/api/users")))
    (is (= "http://www.test.com/api/users" 
           (private-function test-config "http://www.test.com/api/users")))))

Although I could have used the reader macro to refer to the function in the test, I decided to use let to give it a nicer name. I called it private-function to remind myself when I read it later that I am calling a private function. I am pretty happy with this way to test private functions. I considered calling the function function-under-test but that is sort of wordy. I could shorten private-function to private-fn.

After coming up with that solution, I found this post that uses a macro to wrap your private tests making the functions available. That is cool. Downside of using my way of testing private functions if I later decided to make the function public, I’d have to rewrite a bit of the test (it would still pass, but reading the test would be confusing). If I used the macro described in that post, it would be easier, just removing the wrapping function.

Any other suggestions?

Edit: It has been pointed out to me that you probably shouldn’t ever want private functions in Clojure, it doesn’t have the need of encapsulation that OOP programming does. Ok, Cool. But should you need to access a private function, a reader macro is how you would do it…which is the point of this post. :)

Comments