Mocking External Library Functions in Clojure + Speclj

I'm finally 'grok'-ing the approach!

September 1, 2021

I get it!

Ok, coming from Go (which supports polymorphic interfaces), here's how we always approached mocking:

  1. Define an interface for the external thing you need to mock.
  2. Define a simple mock implementation of that interface for use in testing.
  3. If the external thing doesn't already implement the interface defined in step 1, define the wrapper implementation.
  4. Pass the mock implementation to the constructor of the "System Under Test" (SUT) in the test code.
  5. Pass the wrapper implentation to the constructor of the SUT in the production code (usually in main or somewhere close to it).

This is all just a traditional approach to the Dependency Inversion Principle. But this approach doesn't really make sense in a functional (ie. stateless) paradigm.

So, suppose you are working with the Quil GUI/animation library in a Clojure project. You'd like to mock out calls to the quil functions for a few reasons:

  1. These functions literally explode, a word which here means throw exceptions, when they are invoked outside of a running quil proecess (like from tests).
  2. You'd like to prove that the right values are always being passed to the quil drawing functions and that they are being invoked the right number of times.

Reason #2 above is all about being professional, about doing your best to "produce, with each release, a quick, sure, and repeatable proof that every element of the code works as it should." (Item 3, The Programmer's Oath, by Robert Martin.)

So, I've already written about doing basic stubbing with speclj, but this situation is different because of the external quil calls. The answer is to combine the stubbing capabilities of speclj and the (with-redefs) function:

(ns gui.main-spec
    [quil.core :as q]))

; SUT (system under test)
(defn draw []
  (q/fill 42)
  #_"remaining code omitted for the sake of brevity")

; Specs
(describe "mocking (external) quil calls"
  (it "records the provided arguments"
    (let [fill-stubbed (stub :fill)]
      (with-redefs [q/fill fill-stubbed]
        (should-have-invoked :fill {:with [42]})))))

So, here's the functional approach:

  1. Use speclj to establish a stubbed version of the fill function. In this case we aren't concerned with a return value (all of the quil drawing functions are invoked for their side-effects).
  2. Use with-redefs to patch quil's actual fill function with the stub.
  3. Invoke the SUT within the scope of with-redefs.
  4. Assert that the correct arguments were passed to the stubbed fill function.

Phew! Ok, I'm off to the races again.

An interesting distinction between these two testing approaches is this: in the polymorphic, interface-driven approach, the production code references the abstraction of the interface. In the clojure approach, the production code references the concrete, external function directly and the test sneakily patches it with a fake/stub/mock thing.

-Michael Whatcott