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

First Post!

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

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.

Last Post!

yordisprieto

yordisprieto

 ** (RuntimeError) failed to start child with the spec Patch.Supervisor.
     Reason: bad child specification, got: {:already_started, #PID<0.8436.0>}

:sob:

Where Next?

Popular in Discussions Top

chuck
Let me start by stating an assumption: Phoenix is a great approach to building REST APIs. There are many reasons for this, but I will ass...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
pillaiindu
I want to convert a Phoenix LiveView CRUD website to a CRUD mobile app. What do you think is the easiest way to do so?
New
AstonJ
Can you believe the first professionally published Elixir book was published just 8 years ago? Since then I think we’ve seen more books f...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New

Other popular topics Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement