Efx is a library to define and test side effects declaratively. It is basically a very focused mocking framework, reducing implementation overhead and focusing on testable code.
The special thing about it is, that it uses the fairly new ancestor-keys from the process dictionary to track down the bindings for tests event for child-processes and therefore, more tests can be implemented asynchronously.
If you are interested, need more rational or just want to skim the code, you can find out more on our github. We are looking forward to some feedback!
To ask a fairly obviously question, the idea is that any side-effect functions (or just ones you want to test) can be defined with defeffect? So MyModule in the README example is the implementation/domain code? If so that’s super cool.
I am not sure I get the idea at all… are effects completely opt-in and whatever the dev says are effects? I’d expect a library like this to scan my code for e.g. File.write and automatically tag the function calling it as effectful.
I’m glad I’m not the only one who was slightly confused! I think the README could be slightly more explicit in the examples.
But yes, I’ve just been playing with it in a demo app and you simply define your application’s functions with defeffect then you can mock them in tests with bind. It’s not auto-flagging side-effects for you. It feels pretty nice. I kind of want to use defeffect for all side-effect functions even if I don’t mock them in tests (though I probably won’t).
I think it would be clearer if the examples actually had effectul code in the bodies just to drive the point home, especially for those of us who skim code examples on first read-through of READMEs. But like, I figured it out
There are also a couple of small gotchas not explicitly documented: @specs aren’t optional (which makes sense) and zero-arity functions are required to be defined with empty () or else you get a cryptic error. I opened an issue about the latter.
It is a mocking library, that doesn‘t need global binding for your test cases. So all mocking and preparation happens directly in your test code. No need for configs or anything else.
I started using it in my projects and it makes testing clearer and easier.
Thanks for all the valuable feedback! I will definitely put some more love into the documentation to make some things easier to understand.
You figured out most of the stuff yourself. Here’s a small recap and some thoughts on your comments
We simply use a spec combined with defeffect to define effects. In the body of the defeffect function we define our default (production) behavior
In our code base, we have several occasions where we use defeffect to make side-effects explicit but don’t use it for testing. Making side-effects explicit and call them what they are is one purpose of the library
Yes it is a mocking library but focused on the semantics, that we have side-effects. We don’t talk about config, about how to mock, about technicals. Just side-effects.
Generic automatic tagging is impossible. So … we don’t.
We can test more code async than in e.g. mox, without allowance (see this test-case), but not all. The processes under test need the test-process to be an ancestor. E.g. in our code-base we need to set async to false if we test oban jobs, since they are located in the supervision tree of the oban application.
Again thanks for your feedback, that helps us to improve the library.
We just released a new version (0.1.11) of the library. Taking the feedback seriously, we improved documentation in this release. Furthermore, it is now possible to use the defeffect-macro without parenthesis, in case there are no arguments (credits to @sodapopcan for this one).
Another tidbit of feedback: don’t use GitHub links without a commit hash. Just press Y while visiting the link and it should also add a commit hash to the file + line.
Setup bindings once for all tests
We call these bindings omnipresent. We define them usually in our test_helpers.ex. There is an example in the EfxCase doc.
Formatter support
Formatters recently added braces around defeffect one-liners (e.g. in defeffect foo, do: "hello"). You can now include the library in your formatter config and have the formatter tamed (see Usage in Readme).
We released some minor updates (currently at version 0.2.6):
defeffect can have guards, now
defeffect can have catch and rescue in the body defintion
we introduced delegateeffect which basically behaves like defdelegate. Delegates can be bound in tests, same as functions defined with defeffect. This is a foundation for combining and reusing effects and effect-modules (tbd).
It’s very awesome to see this library being used and we’re very grateful for the feedback from you!