Swapping Alternate Implementations with Mount

Getting ready for a talk at Clojure Remote gave me an excuse to work on several mount example apps that, I feel, should help others to start with mount, as well as just present certain ways to structure applications.

Sending SMS over Web


Using a great twilio library I wrote a small web app that sends texts over web (i.e. via HTTP POST). It serves as a good example of how to test mount states by swapping them with stubs/mocks. This application has 3 states:

  • config which is loaded from an external file

  • web-server a Jetty web server

  • send-sms which, once started, becomes a function with Twilio creds that sends texts

The app receives an HTTP POST request and sends an SMS message:

(POST "/sms/:from/:to/:msg" [from to msg]
  (generate-string
    @(send-sms {:from from
                :to to
                :body msg}))))

The

send-sms is a mount state that is setup with Twilio credentials that come from the config:

(defn create-sms-sender [{:keys [sid auth-token]}]
  (fn [{:keys [from to body]}]
    (twilio/with-auth sid auth-token
      (twilio/send-sms 
        (twilio/sms from to body)))))
 
(defstate send-sms :start (create-sms-sender 
                            (:sms config)))

When an HTTP request is sent:

$ curl -X POST "http://localhost:4242/sms/+15104266868/+17180000000/mount%20is%20fun%20:)"

it gets wrapped to an SMS payload and is passed to Twilio which successfully delivers it:

I am sure you noticed, but the Twilio phone number this SMS is sent from is:

+1 (510) 42 MOUNT 🙂

Testing the App


Notice that while send-sms is a state, when started, it becomes just a function that takes args and passes them to Twilio as an SMS payload. Which means that if it is needed to be replaced, or swapped, during testing, it can be replaced with a test function that, for example, receives an SMS and puts in on a core.async channel:

(fn [sms] 
  (go (>! sms-ch sms)))

One thing to note, the real twilio/send-sms returns a future, which means it might be dereferenced somewhere in the codebase, and it is just safer to stay true to the “real thing”, so we’ll return a future as well:

(fn [sms] 
  (go (>! sms-ch sms))
  (future))

Now all that needs to be done is to create a test function and let mount know to use it instead if the real one.

;; ...
(let [sms-ch (chan)
      send-sms (fn [sms]
                 (go (>! sms-ch sms))
                 (future))]                        ;; twilio API returns a future
  (mount/start-with {#'app.sms/send-sms send-sms})
;; ...

This way mount will start an application with swapping the real app.sms/send-sms for a send-sms stub function. Which means that any reference to app.sms/send-sms at runtime will be using this locally scoped send-sms stub.

Check out the working test to get a visual on how all the above pieces come together.
And here is more details on swapping alternate implementations from mount docs.

Tags: , ,