lessless

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

Showing Posts 1 to 10

D4no0

D4no0

If my assumptions are correct, your calculate_shipping depends 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:

def calculate_price(order, shipping_cost_fn \\ &calculate_shipping/1) do
  shipping_cost = shipping_cost_fn.(order)
  ...
end
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.

lessless

lessless OP

Both of those methods requires changes in production code. Especially Mox - in terms of pros & cons it’s a totally different technic.

lessless

lessless OP

DI with Application.get_env is 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 PriceCalculator clients were needed.

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.

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

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

lessless OP

Imo that kind of thinking is a trap. You by definition now have two implementations, not one. It doesn’t matter that only one is used in production.

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 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.

That’s what I call a lot of ceremony for a small-scale need. The ergonomics feels off.

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.

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

LostKobrakai

Yes, because you’re explicitly providing the implementation - through the class name.

The elixir equivalent of that call would be

Shipping.calculate_price(PriceCalculatorStub)

If you consider a hypothetical “before state” of your test it would have looked like this:

assert 1100 == PriceCalculator.new.calculate_price(...)
vs.
assert 1100 == PriceCalculatorStub.new.calculate_price(...)
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.

lessless

lessless OP

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

LostKobrakai

To give a concrete example how this could look like in elixir before and after:

defmodule Shipping do
  def calculate_price() do
    call_api() + 15
  end
end

to

defmodule Shipping do
  def calculate_price(calculator \\ nil) do
    calculated = if calculator, do: calculator.calc(), else: calc()
    calculated + 15
  end

  defp calc() do
    call_api()
  end
end

Where Next? Top

Trending in Discussions Top

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
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
achempion
I’ve been using Emacs as my main code editor for more than a two years. It’s a custom build version although I’ve tried doom emacs and sp...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New
jtormey
Lately I’ve been thinking about how to organize components as a LiveView application grows. One of the pain points I’ve found (for myself...
New
Null-logic-0
What IDE or editor are you using for Elixir development? Personally, I use Zed, and I really like it, but sometimes I wish there were a ...
New

Other Trending Topics Top

KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
CodeSync
Code BEAM Europe 2026 – Call for Talks is Open! We’re excited to announce that Code BEAM Europe 2026 is now open for talk submissions :t...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews