lessless
I wonder if it’s possible to mimic a simple “class reopening”/inhertinace-based SEAM in Elixir to alter a module’s behaviour without editing its code.
Here is an example in pseudo-Ruby
class PriceCalculator
def calculate_price(order)
base_price = calculate_base_price(order)
shipping = calculate_shipping(order)
base_price + shipping
end
def calculate_shipping(order)
ShippingServices.calculate_shipping(order)
end
end
Now the calculate_shipping is enabling point to override the shipping costs calculation behaviour because we can override it in tests without altering the production code.
class Test
class PriceCalculatorStub < Price calculator
def calculate_shipping(order)
1000
end
end
def test_order_price_is_base_price_plus_shipping_price
assert 1100 == PriceCalculatorStub.new.calculate_price(...)
end
end
Example is adapted from Legacy Seam
Trending in Discussions
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
New
The obligatory hello world thread!
Who are you and where are you from? :stuck_out_tongue:
New
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
I was working on an Ecto migration and I needed a timestamp. So, for the nth time, I looked up the different data types for timestamps, a...
New
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog
It says that Fly is going all-in on sprites, which is a worry ...
New
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using.
We’re particularly inte...
New
Is there a word for the ~> symbol used in Version strings?
Do you also just call it a Squiggle Arrow™ ?!
New
Other Trending Topics
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
There are three potential reasons for members of this forum to have a look at https://vutuv.de
You are tired or annoyed of LinkedIn.
Yo...
New
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #elixirconf-us
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
D4no0
If my assumptions are correct, your
calculate_shippingdepends on an external API? If yes, then we universally use mocks for such external calls. There is Mox library that I personally always use and similar ones for doing this.Alternatively, if the things you want to mock in tests are very limited in their scope, you can inject your functions as parameters and later replace them in your tests:
LostKobrakai
There’s two parts here. There’s introducing the interface of the seam and there’s how to switch out the implementation.
For the first one the answer is quite simple. Extract to a function, make the function inputs and outputs the interface. We commonly use Behaviours for that stuff given things usually don’t just need a single callback.
The latter is dependency injection in whatever form you want to do it. Personally I like explicitly passing in dependencies (e.g.
GenServer.start_link(Dependency, …)is just that), but you can go withApplication.get_envor some other global dependency resolver option. For tests specificallyMoxwill allow you to use a single behaviour implementing module to inject dynamic code to run.I’d strongly suggest not getting hung up on the inheritance portion of this. It’s imo irrelevant to what that blog post tries to teach.
I’d also suggest to let loose of the “without altering the production code”. None of the examples of Martin Fowler left the production code untouched.
lessless
Both of those methods requires changes in production code. Especially Mox - in terms of pros & cons it’s a totally different technic.
lessless
DI with
Application.get_envis just too much of a ceremony sometimes. Particularly I consider a behaviour with a single prod implementation that is there just for testing as a lot of ceremony.The example I have shown doesn’t require any code changes for testing. And whatever Martin Fowler did in the blog post didn’t affect the interface - i.e. no changes to the
PriceCalculatorclients were needed.Agreed. It’s a specific technic that satisfies a number of constraints and the whole purpose of my question is to understand if there is a similar technic that we can use in Elixir codebases. Not a DI through explicitly passing collaborators or making them swappable via application config.
I hope it’s clear what I’m asking.
LostKobrakai
Imo that kind of thinking is a trap. You by definition now have at least two implementations, not one. It doesn’t matter that only one is used in production.
That’s fair. But I mentioned you don’t need to go the explicitly passing route. You can just as well start some global process somewhere, which provides the dependency to be used. Call that in your code and you don’t need callers of said code to change. Same with
Application.get_env. That’s also global state you can read without needing callers to be involved. The big piece here is “global state” no matter the technical implementation. The technical path chosen by Martin Fowler was global state through a static class variable.lessless
In the case of the method override one of them is not leaking into production. The changes are contained within the test code.
Which I think is a decent scope for a small-scale need.
That’s what I call a lot of ceremony for a small-scale need. The ergonomics feels off.
Fair point, but not the one I want to debate. In the example I provided there is no global state.
And I’m specifically interested in the compact and ergonomic solutions. Everything we discussed so far default ways in Elixir, they are well-trodden, I wouldn’t come here to ask/hear about a technique I’ve used a hundred times. That’s boring and a waste of time :\
LostKobrakai
Yes, because you’re explicitly providing the implementation - through the class name.
The elixir equivalent of that call would be
If you consider a hypothetical “before state” of your test it would have looked like this:
D4no0
It would be very useful if you pointed that out. To me the question seemed like someone new coming from the ruby world looking ways on how to do things in elixir. A short sentence including the fact that you are aware of how DI is done in functional languages and mocking tools would save us a lot of time.
Generally speaking if the solutions provided by me and @LostKobrakai are a no-go, then you are out of luck, unless you fancy doing some metaprogramming. I personally cannot see how provided solutions are in any way worse than the one you pointed, but I am not interested in arguing on that as it goes into off-topic.
lessless
Yeah, I could have spent a five minutes more to add more context.
I still sense there should be a way to pull off something similar. Maybe with metaprogramming indeed.
LostKobrakai
To give a concrete example how this could look like in elixir before and after:
to