integrant 2023-08-02

hey folks! maybe it’s a silly question: how can I “mock” init methods? I know I can bring up a whole new system given a config… but what if I just want a certain component to be swapped for testing?

❤️ 1

You can use keyword inheritance for this. For example, you might have a key :example/webserver. You could replace this with a key :example.mock/webserver that derives from the original.

That way you only have to replace one key in the configuration, and all the refs will work the same.

that would imply: 1. add new key :example.mock/webserver 2. create the init defmethod for it 3. substitute all deps from :example/webserver to :example.mock/webserver am I seeing this correctly?

Almost: you wouldn't need to substitute all deps if you used derive. You could also create a single :example/mock key and use composite keys: [:example/mock :example/webserver] .

(defmethod :mock/server [_ _]
  {:started? true})

(derive :mock/server :real/server)

(def mock-config
  (-> config
      (assoc :mock/server (:real/server config))
      (dissoc :real/server)))

You could also use a mocking library to mock out init-key.

(require '[clojure.set])
  
  (defmethod ig/init-key :mock/server [_ _]
    (println "MOCK"))

  (defmethod ig/init-key :real/server [_ _]
    (println "REAL"))

  (defmethod ig/init-key :dep/stuff [_ _]
    (println "DEP"))

  (derive :mock/server :real/server)

  (def config
    {:real/server {}
     :dep/stuff (ig/ref :real/server)})
  
  (def mock-config
    (clojure.set/rename-keys config {:real/server :mock/server}))

  (ig/init mock-config)
  (ig/init config)

You probably want (:real-server config) instead of (:real-server {}) (which is always nil ).

You could also use clojure.set/replace-keys in place of assoc/dissoc:

(set/replace-keys config {:real/server :mock/server})

1