Fork me on GitHub
#duct
<
2019-02-01
>
rickmoynihan15:02:15

@weavejester: Have you ever wanted or wished for something like with-redefs but for integrant keys? Thinking for the testing case, where you want to stub out a component in a system. I do this currently by defining a new ig/init-key for the stub, meta-merging a test profile over the system with something like (let [system (ig/init (meta-merge main-system {: (ig/ref :my.test/stub)}] ,,,)) However this requires a bunch of boiler plate defmethods especially when those components then need to contain a shared dep for the test to use; e.g. a atom/promise/core.async channel etc… Then the test needs to look into the system to pull out the references to those extra deps that it wants (e.g. the shared channel) which is pretty gross. It’d be nice to be able to do something that let me just define those stub deps in my local scope, rather than have to use defmethods etc. Any thoughts on this, or alternatives?

rickmoynihan15:02:38

Not quite sure how such a thing might work in practice

rickmoynihan15:02:18

hmmm actually I think I might have thought of a way to do it without requiring integrant features

weavejester16:02:12

@rickmoynihan I don't have any thoughts on stubbing out defmethods, except maybe by using :default or derive. I'd be interested in what you come up with, however.

rickmoynihan16:02:02

Scratch that it turns out it doesn’t work 😞 I’m not sure this is possible without integrant changes… I think the nicest for a user might be for integrant to honor metadata on the RHS of a key that effectively says skip the defmethod constructor and use the value that’s here instead.

weavejester16:02:36

Hmm... I'd need to think about that.

rickmoynihan16:02:47

yeah it definitely needs some thought but was thinking something like this:

(let [stub-chan (async/chan)
      system (ig/init (meta-merge main-system
                                  {: ^:ig/stub (mock-service stub-chan)}] ,,,))

rickmoynihan16:02:31

The problem with defmethod and derive are that they’re static.

rickmoynihan16:02:22

The other option would be to have ig/init take an additional overrides argument map… or have a new ig/init-with-stubs

rickmoynihan16:02:41

that takes a map of stubs

weavejester16:02:29

Is stubbing multiple services really that useful? As opposed to just defining a global fake service to use?

weavejester16:02:04

e.g. :duct.database.sql/hikaricp vs. duct.database.sql/stub

rickmoynihan16:02:52

I think it depends. The test here is an integration test; a large part of what it is testing is the base system config is wired up ok.

rickmoynihan16:02:44

so yes, I can do that… but it’s a complex system; and once you start removing keys from the system and replacing them with others it becomes very hard to verify the test is right

rickmoynihan16:02:40

I have considered doing this but it gets messy as you have to replace the ig/refs to the key; using something like clojure.walk.

rickmoynihan16:02:34

Though I guess I can probably implement a generic swapper that walks and replaces keys easily enough… though for the test case it’s often useful for system values to come from a closure rather than config… Actually this might work with a single defmethod like:

(defmethod ig/init-key ::test [_ test-instance]
  test-instance)
and a walk/replace (whatever it’s called).

weavejester16:02:08

What about building something that simplifies (with-redefs [ig/init-key ...] ...)?

weavejester16:02:00

Like: (with-methods [ig/init-key {::foo (constantly {:a 1})}] ...)

weavejester16:02:23

So an equivalent of with-redefs but for multimethod keys.

weavejester16:02:52

Then it wouldn't have to be tied to Integrant at all.

rickmoynihan17:02:53

That’s an interesting idea

rickmoynihan17:02:29

Should be quite easy too