Do people use with-redefs to mock in tests? A potential problem is that it changes bindings for all threads, so it stops tests parallelization. Perhaps there is some test runner that would run tests concurrently in separate classloaders?
This is the really nice thing about the component architecture. For greenfield development if I found myself having to use with-redefs to test something I would re-architect.
Of course in the real world sometimes you go to war with the army you have. Even then, there are often ways to avoid with-redefs. For the HTTP example above, lately I've been using https://github.com/johanhaleby/stub-http, which spins up a tiny HTTP server in process so that you can run your client code against that.
For Clojure JVM, with-redefs is a really easy way to start testing things. I disagree that with-redefs is only needed when there is global mutable data. I just use it to say "what if this thing returned this data?". i.e. what if the auth system says this token has the incorrect audience. Well, you could bring in a token creating library, set up your own client token signing, manage the resources involved in keeping those certs up to date, etc. Or you could factor out the error that the auth system returns to its own fn and with-redefs your auth fn to just call that error fn and return.
This avoids you needing to duplicate the shape of the error in the tests, which improves longevity. It also allows you to avoid potentially dozens of transient deps for your tests which would come in due to the token work, not to mention all of the code involved in doing that. It'll work for multi-threaded request handling and it's also very clear, in terms of readability, what you're doing in the test code.
I think it's worth pointing out that I'm not aware of a clojure test runner that parallelizes tests across threads, so that particular criticism of with-redefs is somewhat moot.
https://github.com/weavejester/eftest does that, using java.util.concurrent.Executors' FixedThreadPool
People do use with-redefs, but yes you can't use it with multiple threads. Best to pass things as arguments so you can pass on mocks in tests.
this can make very simple code very complicated, e.g. letโs say the leaf functions do HTTP calls
Polylith has a classloader isolation thing (not per test, but per suite) and it is problematic, so at my last job someone ended up writing a thing to run the suites in different processes
thatโs one solution yea
this is one of the benefits of a Component architecture. at a previous job, we had a db component and wrapped the query-one/query-many/execute functions from clojure.jdbc to perform the actual calls or provide mocked data depending on the db
it wasn't ideal but it worked for us pretty neatly
we discussed moving the query functions themselves into the component but that would have been a longer project and i was laid off before we could execute on that
Yes people use with-redefs a lot in tests. Please, don't run tests in parallel as it always causes problems
Note: I'm not saying with-redefs is good though; in fact, it isn't. But it's cheap and simple, so that's why people use it all the time.
If there is a program that can be tested only with with-redefs, then the program has failed the first test! Namely: No global data! On the bright side, if there is a program that can only be tested with with-redefs, then there is not much point in testing it, because it won't last too long before someone has to replace it. ๐
I know with-redefs gets a lot of criticism because of the drawbacks (which are totally valid), and I'm a huge fan of component/reloaded workflows in general, but if you're taking a coarse grained testing approach where your "unit" under test is relatively large (e.g. an entire vertical slice of the whole app), and if test parallelization is done as separate processes (as mentioned above), then I think it's a fine tool for the job.
If you don't mind making your data sources/sinks (aka "leaf functions", as mentioned earlier in this thread) dynamic vars, and you need parallel in-process test execution, binding may be a good alternative.
I like https://github.com/circleci/bond a lot, I can't remember running into any issues with it when running tests in parallel but at my last job we might have had a threadsafe version we were using in house deep_thinking @marciol do you remember
One other consideration that's worth a mention. So far we've been talking about parallel/concurrent test execution, but concerning asynchronous test execution (e.g. ClojureScript running on NodeJS), I would highly advise avoiding the use of with-redefs or binding entirely. It's a complete non-starter.