If you’re using Integrant, it’s common to define components to interact with
external services. If you wanted to interact with AWS SQS (simple queue
service), for example, you would create a component to serve as the SQS client.
It’s also common for components to be modeled using protocols, and for
components to instantiated as records or reified objects that implement those
protocols. The ::es/shrubbery-mock init-key alternative makes it easy for you
to create mocks of those components.
An SQS component might look something like this:
very fake AWS SQS service
(ns integrant-duct-example.shrubbery-mock
(:require [integrant.core :as ig]
[sweet-tooth.endpoint.system :as es]
[shrubbery.core :as shrub])
(:refer-clojure :exclude [take]))
(defprotocol Queue
(add [_ queue-name v])
(take [_]))
(defrecord QueueClient []
Queue
(add [_ queue-name v]
;; AWS interaction goes here
:added)
(take [_]
;; AWS interaction goes here
:taked))
(defmethod ig/init-key ::queue [_ _]
(QueueClient.))
This is what it looks like to interact with the real component:
interacting with the real component
(defmethod es/config ::dev [_]
{::queue {}})
(def real-component (::queue (es/system ::dev)))
(add real-component :foo :bar)
;; =>
:added
(take real-component :foo)
;; =>
:taked
The ::dev config initializes the ::queue component, returning a record that
implements the Queue protocol. When calling add, :added is returned. When
calling take, :take is returned.
This is what it looks like to interact with the mocked component:
interacting with a mocked component
(defmethod es/config ::test [_]
{::queue {::es/init-key-alternative ::es/shrubbery-mock
::es/shrubbery-mock {}}})
(def mocked-component (::queue (es/system ::test)))
(add mocked-component :msgs "hi")
;; =>
nil
(shrub/calls mocked-component)
;; =>
{#function[integrant-duct-example.shrubbery-mock/eval17947/fn--17961/G--17936--17970]
((:msgs "hi"))}
(shrub/received? mocked-component add [:msgs "hi"])
;; =>
true
The ::test config’s ::queue component is initialized using the
::es/shrubbery-mock implementation of the es/init-key-alternative
multimethod. It returns a mock object created by the shrubbery library.
When you call add on the mocked component, it returns nil. You can use
shrubbery’s calls and received? functions to interrogate the mocked object.
What if you need the mocked method to return a value other than nil? Here’s
how you could do that:
mock values
(defmethod es/config ::test-2 [_]
{::queue {::es/init-key-alternative ::es/shrubbery-mock
::es/shrubbery-mock {:add :mock-added}}})
(def mocked-component-2
(::queue (es/system ::test-2)))
(add mocked-component-2 :msgs "hi")
;; =>
:mock-added
The map {:add :mock-added} tells shrubbery what values to return for mocked
methods. The keyword :add corresponds to the Queue protocol’s add method,
and that’s why the method call returns :mock-added.
|
|
You can also make use of the second argument to es/system:
es/system anonymous profile
(def mocked-component-3
(::queue (es/system ::test {::queue {::es/shrubbery-mock {:add :mock-added}}})))
|
The sweet-tooth.endpoint.system namespace includes a mocking components,
shrubbery-mock. Instead of
full mock config
{::es/init-key-alternative ::es/shrubbery-mock
::es/shrubbery-mock {:add :mock-added}}
shrubbery-mock helper
(es/shrubbery-mock {:mock {:add :mock-added}})
it expands to the map above.