lessless
Testing "seam" in Elixir
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
Most Liked
sodapopcan
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 with Application.get_env or some other global dependency resolver option. For tests specifically Mox will 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.
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.
Popular in Discussions
Other popular topics
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
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








