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
Curious, I looked at the source of
1 2 3 4 5 6
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
To call the private function outside of the namespace where it is defined, you call it with the reader macro like this:
How I wrote the test with the private function:
1 2 3 4 5 6 7 8
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
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. :)