lessless

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

sodapopcan

There’s also Efx. It’s a cool library. Personally I found myself going back to Mox, though (not that that matters).

LostKobrakai

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

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.

D4no0

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.

Blond11516

Blond11516

You might want to look at patch, which lets you mock functions without behaviours or DI.

Where Next?

Popular in Discussions Top

marciol
Thinking about how Contexts on Phoenix 1.3 are beautiful in the simplicity, elegance and minimalism, coalesced in the smallest unity of E...
New
jeramyRR
Is it really necessary for the compiler to spit out a warning about using @doc with private functions? I don’t want to have to document ...
New
billylanchantin
If you’re not familiar with this fun bit of math lore, Paul Erdős, the famously eccentric, peripatetic and prolific 20th-century mathem...
New
markmark206
Every time I build a web app, I worry about a bunch of basic things (persisting data, knowing when to compute which things, keeping thing...
New
AstonJ
A remark @Garrison made… ..has already inspired a thread for the technical side of reclaiming our internet, but there is of course anot...
New
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 edit...
New
jonator
FLAME is basically the dream for running AI agents that need system command access such as coding agents. However, I have concerns about ...
New
axelson
Hi there! :wave: @frigidcode and I (but mostly him) have been running an Elixir Book club, we’re almost done with Designing Elixir Syste...
New
AstonJ
Posting this as a separate thread as I’m sure lots of people will be curious - what will the (or the intended) differences be and when mi...
New
sergio
Laravel just announced their Series A round for $57 million. If Laravel wasn’t already the defacto PHP stack, it now most certainly is. T...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement